Django ships with a full-featured testing framework built on Python's unittest module. Every Django project gets a test runner, a test client that simulates HTTP requests, and a set of assertion helpers designed specifically for web applications. Testing in Django means you can verify models, views, forms, and template rendering without ever opening a browser.
The framework provides three base test classes — SimpleTestCase, TestCase, and TransactionTestCase — each offering a different trade-off between speed and database isolation. Django 6 introduces forkserver multiprocessing support in DiscoverRunner (Python 3.12+), which can significantly speed up parallel test execution on large codebases by avoiding the overhead of forking a fully-initialized process for every test worker.
Master django testing
Take the Django Testing & Quality Assurance course with hands-on lessons and challenges.
The test client is available as self.client on every TestCase. force_login() authenticates without going through the login form. assertContains checks both status code (200 by default) and that the text appears in the response body. assertRedirects follows the redirect chain and verifies the final destination.
assertContains verifies text appears in the rendered HTML and that the status is 200. assertTemplateUsed confirms which template was rendered. assertRedirects follows the 302 and checks the final URL. Always use reverse() instead of hardcoded paths so tests survive URL changes.
full_clean() triggers all field validators and the custom clean() method — save() skips validation by default. refresh_from_db() reloads the object after database changes. setUpTestData creates the category once for the entire class since it is read-only, making the test class faster than using setUp.
Always patch where the name is looked up, not where it is defined — 'myapp.services.requests.get', not 'requests.get'. side_effect raises an exception when the mock is called, letting you test error paths. return_value sets a fixed response. assert_called_once_with verifies both that the mock was called and with the correct arguments.
Test forms by instantiating them with a data dict and calling is_valid(). Check form.errors for field-specific messages and '__all__' for non-field errors raised in clean(). Always test both valid and invalid inputs, including edge cases like duplicate values and cross-field validation.
Using `TestCase` when `SimpleTestCase` would suffice — every TestCase sets up a database transaction even if the test never touches the database
Use `SimpleTestCase` for tests that only exercise pure functions, template tags, URL resolution, or validators. It skips database setup entirely and runs significantly faster. Reserve `TestCase` for tests that actually create or query model instances.
Not using `setUpTestData()` for shared read-only data — creating the same user and category objects in `setUp()` before every single test method
Move read-only fixtures (users, categories, configuration objects) into `setUpTestData()`, which runs once per class instead of once per test. Keep `setUp()` only for data that individual tests will modify. This alone can cut test suite time in half on large projects.
Calling `save()` and assuming validation ran — Django's `save()` does not call `full_clean()` by default, so invalid data can reach the database without raising a `ValidationError`
Always call `full_clean()` explicitly in tests to verify model validation. Write tests like `with self.assertRaises(ValidationError): article.full_clean()` to confirm that your validators actually reject bad data.
Patching where a function is defined instead of where it is imported — e.g., `@patch('utils.send_email')` when the view does `from utils import send_email`
Patch the name in the module that imports it: `@patch('myapp.views.send_email')`. Python looks up names in the importing module's namespace, so patching the source module has no effect on the already-imported reference.
Django's testing framework gives you three test base classes (SimpleTestCase, TestCase, TransactionTestCase), a built-in HTTP client, and web-specific assertions like assertContains, assertRedirects, and assertTemplateUsed. Test models with full_clean() for validation and refresh_from_db() after mutations. Test views through the test client with force_login() for authentication. Mock external dependencies with unittest.mock's @patch decorator, always patching where the name is looked up. Use setUpTestData() for shared read-only fixtures and setUp() only for mutable data. Django 6 adds forkserver multiprocessing to DiscoverRunner on Python 3.12+, improving parallel test performance on large suites.
Interactive lessons and challenges on Stanza, practice in VS Code, Cursor, or the web.
Interactive lessons and challenges, right in your code editor.
Check the free courses. No credit card.