Testing Django Apps
🧪 Testing Django Apps
unittest, with one standout feature most other stacks make you configure yourself: every test's database changes vanish automatically when the test ends.
TestCase Basics
A Django test is a class extending django.test.TestCase, with test methods starting with test_ — everything from unittest (assertEqual, assertTrue, ...) is available directly:
🔄 Automatic Per-Test Rollback
The single biggest difference from a typical FastAPI/pytest testing setup: TestCase wraps every test method in a database transaction and rolls it back the instant the test finishes — no manual cleanup, no shared state leaking between tests:
Manual Cleanup vs Automatic Rollback
A Typical Manual Setup
Create test data in a fixture/setup step, run the test, then explicitly delete or reset the rows afterward — often in a teardown function you have to remember to write and keep correct.
Django's TestCase
Every test_* method runs inside its own transaction; Django rolls it back automatically when the method returns — objects created in setUp() or the test itself are gone before the next test starts, with zero cleanup code.
setUp()
Runs before every test method in the class — the natural place for data every test in the class needs, since it's re-created fresh (and rolled back) per test.
Tests Never Pollute Each Other
Because each test's changes roll back, test order never matters and one test's leftover data can never silently affect another's assertions.
The Test Client
self.client simulates real HTTP requests against your views — no running server required:
response.context
Direct access to the exact context dict passed to render() — a genuinely Django-specific testing convenience, letting you assert on the data a view produced, not just the rendered HTML string.
assertTemplateUsed
Confirms which template actually rendered the response — catches a view that returns the right data through the wrong template.
Fixtures
A fixture is a JSON (or YAML) file of pre-defined data, loadable into the test database — useful for larger, shared datasets, though many Django projects prefer building data directly in setUp() (as this chapter has done) for anything test-specific and easy to read at a glance:
Testing Forms Directly
A form (or a DRF serializer, from the last chapter) can be tested without a client or a real request at all:
💻 Coding Challenges
Challenge 1: Write a Model Test
Write a TestCase for the Author model that creates an author in setUp() and asserts str(author) returns the author's name.
Goal: Practice the basic setUp() + test_* method structure.
Challenge 2: Write a View Test With the Test Client
Write a test that uses self.client to GET a book detail view, asserting the response status is 200, the correct template was used, and the response contains the book's title.
Goal: Practice self.client.get(), assertTemplateUsed, and assertContains together.
Challenge 3: Prove the Rollback Behavior
Write two test methods in the same TestCase class: the first creates a Book and asserts Book.objects.count() == 1; the second (with no setUp() creating any books) asserts Book.objects.count() == 0. Explain why the second test passes even though it runs after the first.
Goal: Practice reasoning about — and directly observing — the per-test transaction rollback.
The automatic per-test cleanup is specifically a database transaction rollback — it does nothing for Django's cache framework, module-level variables, or any other in-memory state a test might touch. A test that warms the cache (covered in the next-but-one chapter) or mutates a global will leak that state into the next test regardless of TestCase's rollback. Also watch for accidentally subclassing plain unittest.TestCase instead of django.test.TestCase — the plain version has no database transaction wrapping at all, and a test that touches the ORM under it will leave real, un-rolled-back rows behind for every test that runs afterward.
🎯 What's Next
With a real test suite in place, the next chapter looks at code that runs around every request rather than inside a specific view: Middleware & Signals — Django's request/response middleware chain and the signal-dispatch system for decoupled event handling.