Testing Django Applications¶
Write comprehensive tests to ensure your Django application works correctly.
Test Types¶
Django supports several types of tests: - Unit Tests: Test individual components - Integration Tests: Test component interactions - Functional Tests: Test user workflows - Performance Tests: Test application performance
Basic Testing¶
Creating Tests¶
# tests.py
from django.test import TestCase
from .models import Article
class ArticleModelTest(TestCase):
def setUp(self):
self.article = Article.objects.create(
title='Test Article',
content='Test content'
)
def test_article_creation(self):
self.assertEqual(self.article.title, 'Test Article')
self.assertTrue(isinstance(self.article, Article))
def test_article_str(self):
self.assertEqual(str(self.article), 'Test Article')
Running Tests¶
# Run all tests
python manage.py test
# Run specific app
python manage.py test myapp
# Run specific test
python manage.py test myapp.tests.ArticleModelTest
# Run with verbosity
python manage.py test --verbosity=2
# Keep test database
python manage.py test --keepdb
Testing Views¶
from django.test import TestCase, Client
from django.urls import reverse
class ArticleViewTest(TestCase):
def setUp(self):
self.client = Client()
self.article = Article.objects.create(
title='Test Article',
content='Content'
)
def test_article_list_view(self):
response = self.client.get(reverse('article_list'))
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Test Article')
self.assertTemplateUsed(response, 'articles/list.html')
def test_article_detail_view(self):
url = reverse('article_detail', args=[self.article.id])
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertContains(response, self.article.title)
def test_article_create_view(self):
data = {
'title': 'New Article',
'content': 'New content'
}
response = self.client.post(reverse('article_create'), data)
self.assertEqual(response.status_code, 302)
self.assertEqual(Article.objects.count(), 2)
Testing Forms¶
from django.test import TestCase
from .forms import ArticleForm
class ArticleFormTest(TestCase):
def test_valid_form(self):
data = {
'title': 'Valid Title',
'content': 'Valid content'
}
form = ArticleForm(data=data)
self.assertTrue(form.is_valid())
def test_invalid_form(self):
data = {'title': ''} # Missing required field
form = ArticleForm(data=data)
self.assertFalse(form.is_valid())
self.assertIn('title', form.errors)
def test_form_validation(self):
data = {
'title': 'abc', # Too short
'content': 'Content'
}
form = ArticleForm(data=data)
self.assertFalse(form.is_valid())
Testing Models¶
class ArticleModelTest(TestCase):
def test_save_and_retrieve(self):
article = Article()
article.title = 'Test'
article.save()
saved_article = Article.objects.first()
self.assertEqual(saved_article.title, 'Test')
def test_default_values(self):
article = Article.objects.create(title='Test')
self.assertFalse(article.published)
def test_model_methods(self):
article = Article.objects.create(
title='Test Article',
slug='test-article'
)
self.assertEqual(article.get_absolute_url(), '/articles/test-article/')
Testing Authentication¶
from django.contrib.auth.models import User
class AuthenticationTest(TestCase):
def setUp(self):
self.user = User.objects.create_user(
username='testuser',
password='testpass123'
)
def test_login(self):
logged_in = self.client.login(
username='testuser',
password='testpass123'
)
self.assertTrue(logged_in)
def test_protected_view(self):
# Without login
response = self.client.get(reverse('dashboard'))
self.assertEqual(response.status_code, 302)
# With login
self.client.force_login(self.user)
response = self.client.get(reverse('dashboard'))
self.assertEqual(response.status_code, 200)
Testing APIs¶
from rest_framework.test import APITestCase
from rest_framework import status
class ArticleAPITest(APITestCase):
def setUp(self):
self.user = User.objects.create_user(
username='testuser',
password='testpass'
)
def test_create_article(self):
self.client.force_authenticate(user=self.user)
data = {'title': 'New Article', 'content': 'Content'}
response = self.client.post('/api/articles/', data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
def test_get_articles(self):
response = self.client.get('/api/articles/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
Test Coverage¶
# Install coverage
pip install coverage
# Run tests with coverage
coverage run --source='.' manage.py test
# View coverage report
coverage report
# Generate HTML report
coverage html
# .coveragerc
[run]
omit =
*/migrations/*
*/tests/*
*/venv/*
manage.py
[report]
exclude_lines =
pragma: no cover
def __repr__
raise AssertionError
raise NotImplementedError
Fixtures¶
# fixtures/articles.json
[
{
"model": "myapp.article",
"pk": 1,
"fields": {
"title": "Test Article",
"content": "Test content"
}
}
]
# In tests
class ArticleTest(TestCase):
fixtures = ['articles.json']
def test_article_from_fixture(self):
article = Article.objects.get(pk=1)
self.assertEqual(article.title, 'Test Article')
Mocking¶
from unittest.mock import patch, Mock
class EmailTest(TestCase):
@patch('myapp.tasks.send_email')
def test_send_welcome_email(self, mock_send_email):
user = User.objects.create_user('test', 'test@example.com')
send_welcome_email(user)
mock_send_email.assert_called_once()
@patch('requests.get')
def test_api_call(self, mock_get):
mock_get.return_value.json.return_value = {'status': 'ok'}
result = fetch_external_data()
self.assertEqual(result['status'], 'ok')
Performance Testing¶
from django.test.utils import override_settings
import time
class PerformanceTest(TestCase):
def test_query_count(self):
with self.assertNumQueries(1):
list(Article.objects.all())
def test_response_time(self):
start = time.time()
response = self.client.get('/api/articles/')
end = time.time()
self.assertLess(end - start, 1.0) # Less than 1 second
Best Practices¶
- Test early and often
- Aim for high coverage (80%+)
- Write independent tests
- Use descriptive test names
- Follow AAA pattern: Arrange, Act, Assert
- Use factories for test data
- Mock external services
- Test edge cases
- Keep tests fast
- Run tests before committing