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!