Django & Flask Topic

Testing in Django

  • Catch bugs early before they reach production

  • Refactor with confidence knowing your changes don't break existing functionality

  • Document your code through test cases

  • Save time in the long run by preventing regression bugs

Django's Testing Framework

Django extends Python's unittest framework with additional features tailored for web development.

1. Basic Test Structure

python

# tests.py in your app
from django.test import TestCase
from django.urls import reverse
from .models import YourModel
from .forms import YourForm

class YourModelTest(TestCase):
    def setUp(self):
        """Set up data for the tests"""
        self.obj = YourModel.objects.create(
            name="Test Object",
            description="Test description"
        )
    
    def test_model_creation(self):
        """Test that objects are created properly"""
        self.assertEqual(self.obj.name, "Test Object")
        self.assertTrue(isinstance(self.obj, YourModel))
    
    def test_string_representation(self):
        """Test the __str__ method"""
        self.assertEqual(str(self.obj), "Test Object")

2. Testing Models

python

from django.test import TestCase
from myapp.models import Post, User

class PostModelTest(TestCase):
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser', 
            password='testpass123'
        )
        self.post = Post.objects.create(
            title='Test Post',
            content='Test content',
            author=self.user
        )
    
    def test_post_creation(self):
        self.assertEqual(self.post.title, 'Test Post')
        self.assertEqual(self.post.author.username, 'testuser')
    
    def test_post_slug_auto_generation(self):
        self.assertEqual(self.post.slug, 'test-post')
    
    def test_get_absolute_url(self):
        expected_url = reverse('post_detail', kwargs={'slug': self.post.slug})
        self.assertEqual(self.post.get_absolute_url(), expected_url)

3. Testing Views

python

from django.test import TestCase, Client
from django.urls import reverse
from django.contrib.auth import get_user_model

class PostViewTest(TestCase):
    def setUp(self):
        self.client = Client()
        self.user = get_user_model().objects.create_user(
            username='testuser',
            email='test@email.com',
            password='testpass123'
        )
        self.post = Post.objects.create(
            title='Test Post',
            content='Test content',
            author=self.user
        )
    
    def test_post_list_view(self):
        """Test that post list page loads correctly"""
        response = self.client.get(reverse('post_list'))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'Test Post')
        self.assertTemplateUsed(response, 'blog/post_list.html')
    
    def test_post_detail_view(self):
        """Test that post detail page loads correctly"""
        response = self.client.get(reverse('post_detail', kwargs={'slug': self.post.slug}))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'Test content')
    
    def test_post_create_view_requires_login(self):
        """Test that unauthorized users are redirected to login"""
        response = self.client.get(reverse('post_create'))
        self.assertEqual(response.status_code, 302)  # Redirect
        self.assertRedirects(response, '/accounts/login/?next=/post/create/')
    
    def test_post_create_view_authenticated(self):
        """Test that authenticated users can access create view"""
        self.client.login(username='testuser', password='testpass123')
        response = self.client.get(reverse('post_create'))
        self.assertEqual(response.status_code, 200)

4. Testing Forms

python

from django.test import TestCase
from .forms import ContactForm, PostForm

class ContactFormTest(TestCase):
    def test_valid_form(self):
        """Test form with valid data"""
        form_data = {
            'name': 'John Doe',
            'email': 'john@example.com',
            'message': 'This is a test message'
        }
        form = ContactForm(data=form_data)
        self.assertTrue(form.is_valid())
    
    def test_invalid_form(self):
        """Test form with invalid data"""
        form_data = {
            'name': '',
            'email': 'invalid-email',
            'message': 'Short'
        }
        form = ContactForm(data=form_data)
        self.assertFalse(form.is_valid())
        self.assertIn('name', form.errors)
        self.assertIn('email', form.errors)

5. Using the Test Client

python

class TestClientExamples(TestCase):
    def setUp(self):
        self.client = Client()
        self.user = get_user_model().objects.create_user(
            username='testuser',
            password='testpass123'
        )
    
    def test_login(self):
        """Test user login with test client"""
        login_successful = self.client.login(
            username='testuser', 
            password='testpass123'
        )
        self.assertTrue(login_successful)
        
        # Test accessing a protected view
        response = self.client.get('/protected-page/')
        self.assertEqual(response.status_code, 200)
    
    def test_post_request(self):
        """Test making a POST request"""
        self.client.login(username='testuser', password='testpass123')
        
        response = self.client.post(reverse('post_create'), {
            'title': 'New Post',
            'content': 'New content',
            'status': 'published'
        })
        
        # Check if redirect happened (common after successful form submission)
        self.assertEqual(response.status_code, 302)
        
        # Verify the object was created
        self.assertEqual(Post.objects.count(), 1)
        self.assertEqual(Post.objects.first().title, 'New Post')

6. Fixtures

python

# Using fixtures for complex test data
class FixtureTest(TestCase):
    fixtures = ['users.json', 'posts.json']  # Load these files before tests
    
    def test_with_fixtures(self):
        user_count = get_user_model().objects.count()
        post_count = Post.objects.count()
        self.assertGreater(user_count, 0)
        self.assertGreater(post_count, 0)

# Alternatively, use setUpTestData for class-level data
class setUpTestDataExample(TestCase):
    @classmethod
    def setUpTestData(cls):
        """Set up data for the whole TestCase (runs once)"""
        cls.user = get_user_model().objects.create_user(
            username='testuser',
            password='testpass123'
        )
        # Create multiple posts
        for i in range(5):
            Post.objects.create(
                title=f'Post {i}',
                content=f'Content {i}',
                author=cls.user
            )
    
    def test_post_count(self):
        self.assertEqual(Post.objects.count(), 5)

7. Testing Best Practices

python

# tests.py - Organized testing structure
class AdvancedTesting(TestCase):
    def test_custom_model_method(self):
        """Test methods you add to your models"""
        post = Post.objects.create(
            title='Test',
            content='Content here',
            author=self.user
        )
        
        # Test a custom method
        self.assertEqual(post.get_excerpt(), 'Content here...')
    
    def test_error_handling(self):
        """Test how your code handles errors"""
        # Test accessing non-existent object
        response = self.client.get(reverse('post_detail', kwargs={'slug': 'non-existent'}))
        self.assertEqual(response.status_code, 404)
    
    def test_context_data(self):
        """Test the context data passed to templates"""
        response = self.client.get(reverse('post_list'))
        self.assertIn('posts', response.context)
        self.assertIn('page_title', response.context)

8. Running Tests

bash

# Run all tests
python manage.py test

# Run tests for a specific app
python manage.py test myapp

# Run specific test case
python manage.py test myapp.tests.PostModelTest

# Run specific test method
python manage.py test myapp.tests.PostModelTest.test_post_creation

# Run with verbose output
python manage.py test --verbosity=2

# Run tests and show coverage
coverage run manage.py test
coverage report

9. Test File Organization

For larger projects, organize tests in a tests package:

text

myapp/
    tests/
        __init__.py
        test_models.py
        test_views.py
        test_forms.py
        test_urls.py
        factories.py  # For test data factories

Key Testing Concepts Summary

  • TestCase: Base class for all Django tests

  • setUp(): Method run before each test

  • setUpTestData(): Class method run once for the test case

  • Test Client: Simulates browser requests

  • Assertions: assertEqual(), assertTrue(), assertContains(), etc.

  • Fixtures: Pre-loaded data for tests

  • Mocking: Replacing real objects with test doubles (using unittest.mock)

Start by testing your models and forms, then move to views. As you practice, you'll develop a "testing mindset" that will make you a much more confident and effective Django developer!

Comments (0)

No comments yet. Be the first to comment!

LecturreAI Assistant

Hello! I'm LecturreAI

What would you like to learn today?