Introduction to Unit Testing in Django
This article introduces unit testing in Django and explains how to verify models, forms, views, URLs, authentication, permissions, email, file uploads, and database behavior. It covers Django’s test classes, the test client, setup methods, assertions, mocking, fixtures, test organization, common mistakes, and practical testing workflows.
Introduction to Unit Testing in Django
A Django application can appear to work correctly while still containing hidden problems.
A model method may return the wrong value for unusual data. A form may accept input that should be rejected. A protected view may accidentally become available to anonymous visitors. A change in one part of the application may silently break another feature.
Manual testing can catch some of these problems, but repeatedly checking every page and workflow becomes impractical as an application grows.
Automated tests solve this problem by running code and checking that it behaves as expected.
Django includes a test framework built on Python’s standard unittest module. It also provides additional tools for testing models, forms, views, templates, databases, authentication, email, and HTTP responses.
What Is a Unit Test?
A unit test checks one small piece of application behavior.
The unit might be:
- a function
- a model method
- a form validator
- a view
- a permission rule
- a serializer method
- a utility class
A basic test follows three steps:
⧉
1 2 3 4 5 6 7 8 | |
For example:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 | |
The test arranges two numbers, calls the function, and asserts that the result equals 5.
Why Write Tests?
Tests provide a repeatable way to verify application behavior.
They are useful when:
- adding a new feature
- fixing a bug
- refactoring existing code
- upgrading Django
- changing database models
- reviewing another developer’s work
- deploying to production
A test suite acts as a safety net.
Suppose a model method currently works:
⧉
1 2 | |
Later, someone modifies it:
⧉
1 2 | |
The application may still run, but the calculation is now wrong.
A test detects the change:
⧉
1 2 3 4 5 6 7 | |
Tests are especially valuable during refactoring because they help confirm that behavior remains unchanged even when the implementation is reorganized. Django’s documentation describes automated tests as a way to validate new code and detect unintended changes to existing behavior.
Tests Do Not Prove That an Application Has No Bugs
A passing test suite proves only that the tested cases passed.
It does not prove that:
- every possible case was tested
- every requirement is correct
- the interface is easy to use
- the application is secure
- production configuration is correct
- third-party services will always work
Tests reduce risk, but their value depends on what they check.
A test suite containing only easy or irrelevant cases may pass while important workflows remain broken.
Good tests focus on behavior that matters.
Django’s Test Classes
Django provides several test-case classes.
| Test class | Common use |
|---|---|
SimpleTestCase |
Code that does not need database access |
TestCase |
Most tests that use the database |
TransactionTestCase |
Tests involving transaction behavior |
LiveServerTestCase |
Tests requiring a running development-style server |
StaticLiveServerTestCase |
Live-server tests that also need static files |
For most application tests involving models, views, or forms, django.test.TestCase is the usual starting point. Django wraps TestCase tests in transactions to provide isolation and efficient database cleanup. Tests that need to examine transaction behavior directly should use TransactionTestCase instead.
SimpleTestCase
Use SimpleTestCase when the test does not need the database.
⧉
1 2 3 4 5 6 7 8 | |
By default, SimpleTestCase prevents database queries.
This helps make it clear that the test is meant to be independent of the database.
Common uses include:
- pure functions
- utility methods
- URL resolution
- template rendering without models
- validation that does not query the database
TestCase
Use TestCase for most tests that interact with Django models or the database.
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
Django creates a separate test database, runs the tests against it, and isolates database changes between tests. Your normal development or production database should not be used as the test data store.
Where Django Finds Tests
A new Django app usually contains a tests.py file:
⧉
1 2 3 4 5 6 7 | |
Tests can be written directly in this file:
⧉
1 2 3 4 5 6 | |
For a larger app, use a tests package:
⧉
1 2 3 4 5 6 7 8 9 10 | |
Test filenames should normally begin with:
⧉
1 | |
Examples:
⧉
1 2 3 | |
Test methods should also begin with test:
⧉
1 2 | |
Django’s default test discovery follows Python’s unittest conventions.
Running Tests
Run all tests with:
⧉
1 | |
Django discovers tests in the installed applications and runs them.
Typical output looks like:
⧉
1 2 3 4 5 6 7 8 | |
A dot represents a passing test:
⧉
1 | |
An F represents a failed assertion:
⧉
1 | |
An E represents an unexpected error:
⧉
1 | |
Running Tests for One App
Run only the tests in one app:
⧉
1 | |
Run one module:
⧉
1 | |
Run one test class:
⧉
1 2 | |
Run one method:
⧉
1 2 | |
Running a focused subset is useful while developing one feature.
Increasing Test Output
Use greater verbosity:
⧉
1 | |
This displays more information, including individual test names and database setup activity.
Short form:
⧉
1 | |
Keeping the Test Database
Creating the test database can add time to repeated test runs.
Use:
⧉
1 | |
Django keeps the test database after the run and reuses it later when possible. The test infrastructure supports retaining an existing test database through the keepdb option.
Test Method Names
A test method should describe the behavior it checks.
Less useful:
⧉
1 2 | |
More useful:
⧉
1 2 | |
⧉
1 2 | |
⧉
1 2 | |
A descriptive name makes failures easier to understand.
Assertions
Assertions check whether a result matches the expected behavior.
Common assertions include:
| Assertion | Purpose |
|---|---|
assertEqual(a, b) |
Values are equal |
assertNotEqual(a, b) |
Values are different |
assertTrue(value) |
Value is true |
assertFalse(value) |
Value is false |
assertIsNone(value) |
Value is None |
assertIsNotNone(value) |
Value is not None |
assertIn(item, collection) |
Item exists in collection |
assertNotIn(item, collection) |
Item does not exist |
assertRaises() |
Code raises an exception |
assertContains() |
Response contains text |
assertNotContains() |
Response does not contain text |
assertRedirects() |
Response redirects correctly |
assertTemplateUsed() |
A template was rendered |
assertFormError() |
A form contains an expected error |
Examples:
⧉
1 | |
⧉
1 | |
⧉
1 2 3 4 | |
A test should fail when the application’s behavior differs from the requirement.
Testing a Model
Consider this model:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Tests can check its default values and methods:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | |
These tests focus on the behavior defined by the model.
Testing Database Constraints
Suppose an article slug must be unique:
⧉
1 2 3 | |
Test the database rule:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
Database constraints are important because model forms are not the only way records can be created.
setUp()
Use setUp() to prepare data before every test method.
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
setUp() runs before each test.
Each test receives fresh test state because database changes are isolated.
setUpTestData()
Django’s TestCase provides setUpTestData() for data shared by every test method in a class.
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | |
setUpTestData() runs once for the class, while setUp() runs before every test method. Creating shared database records in setUpTestData() can make a test class faster.
Use setUp() when each test needs newly prepared mutable state.
Use setUpTestData() when the same mostly unchanged records can be shared.
Avoid Depending on Test Order
Tests should run independently.
Do not write tests like this:
⧉
1 2 3 4 5 6 7 8 9 10 | |
The second test depends on the first.
Instead, create the required data in each test or in a setup method:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
Django supports options such as --shuffle and --reverse that can help reveal accidental dependencies on test execution order.
Testing Forms
Consider a form:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
Test valid data:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Test invalid data:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Test the error message:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
Testing Views
Django provides a test client that behaves like a lightweight browser.
It can make requests to Django views without starting the development server.
Every Django test case has access to:
⧉
1 | |
The client is recreated for each test, so cookies and other client state do not automatically leak between test methods.
Consider this view:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Test the response:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
Use reverse() in Tests
Avoid hard-coding URLs:
⧉
1 | |
Prefer named URL reversal:
⧉
1 2 3 4 5 6 | |
This keeps tests working when the URL path changes but its name remains the same.
Testing the Template
Check that the correct template was used:
⧉
1 2 3 4 5 6 7 8 9 | |
Testing Template Content
Check that text appears in the response:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Check that content is absent:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Testing Context Data
Inspect the context passed to the template:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
Testing a Detail View
Suppose the URL is:
⧉
1 2 3 4 5 | |
Test an existing object:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
Test a missing object:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 | |
Testing POST Requests
Suppose a view creates an article:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Also check the redirect:
⧉
1 2 3 4 | |
Test invalid input:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
An invalid form normally returns the form page with validation errors rather than redirecting.
Testing Class-Based Views
Class-based views are tested through their URLs in the same way as function-based views.
⧉
1 2 3 4 5 6 7 8 9 10 | |
Testing through the URL exercises:
- URL routing
- middleware
- the view
- template rendering
- response generation
For isolated view testing, Django also provides RequestFactory, but the test client is usually simpler for beginner-level view tests. The client is intended to simulate requests and inspect response status, content, redirects, templates, and context without requiring a running server.
Testing Authentication
Create a user with the configured user model:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
Use create_user() rather than assigning a plain-text password directly.
Logging In Through the Test Client
Use:
⧉
1 2 3 4 | |
Check the result:
⧉
1 | |
Then request a protected view:
⧉
1 2 3 | |
Using force_login()
When the login process itself is not being tested, use:
⧉
1 | |
This logs in the user without checking the password through the authentication backend.
Example:
⧉
1 2 3 4 5 6 7 8 9 10 11 | |
Testing Anonymous Access
Test that an anonymous user is redirected:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
This verifies that the protection exists at the view level.
Testing Permissions
Suppose a view requires:
⧉
1 | |
Create the permission:
⧉
1 2 3 4 5 6 7 8 | |
Test permitted access:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
Also test the negative case:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
The expected response depends on the view’s permission configuration.
Testing Ownership Rules
Permission tests should also cover object ownership when relevant.
Suppose users may edit only their own articles:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | |
Test both what users are allowed to do and what they must not be allowed to do.
Testing URLs
Use resolve() to test URL routing:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
For a class-based view:
⧉
1 2 3 4 | |
URL tests are most useful when routing itself contains meaningful complexity.
Testing Exceptions
Use assertRaises() when code should reject an invalid operation.
⧉
1 2 3 4 5 6 | |
You can also inspect the message:
⧉
1 2 3 4 5 6 7 8 9 | |
Testing Email
Django replaces normal email delivery with an in-memory test outbox during tests.
Suppose a function sends an email:
⧉
1 2 3 4 5 6 7 8 9 10 | |
Test it:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | |
Django’s test environment installs a dummy email outbox so tests can inspect messages without sending real email.
Fixtures
A fixture contains predefined data that Django can load into the test database.
Example file:
⧉
1 | |
⧉
1 2 3 4 5 6 7 8 9 10 11 | |
Load it in a test:
⧉
1 2 3 4 5 6 7 8 9 10 11 | |
Django loads declared fixtures before the tests use them. For TestCase, fixture data is loaded for the class and database isolation prevents test methods from affecting one another.
Fixtures can be useful for stable reference data, but they may become difficult to maintain when models change.
For many tests, creating only the required objects directly is clearer.
Factories and Helper Functions
Repeated model creation can be placed in a helper:
⧉
1 2 3 4 5 6 7 8 9 | |
Use it in tests:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Helper functions keep tests concise while making test data explicit.
Third-party factory libraries can provide more features, but beginners should first understand ordinary model creation and setup methods.
Mocking External Services
Unit tests should not normally make real network requests or contact production services.
Suppose a service calls an external API:
⧉
1 2 3 4 5 | |
Mock the dependency:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
Mock where the dependency is used, not necessarily where it was originally defined.
Mocks are useful for:
- external APIs
- email service wrappers
- payment providers
- file storage
- background task dispatch
- slow or unreliable dependencies
Avoid mocking so much that the test no longer exercises meaningful application behavior.
Testing Time-Dependent Behavior
Code involving the current time can produce fragile tests.
Suppose an article is considered recent for seven days:
⧉
1 2 3 4 5 6 7 8 9 | |
A test can create controlled data:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
For complex time behavior, freeze or mock time through a well-defined boundary.
Testing File Uploads
Use SimpleUploadedFile:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | |
Tests involving storage should clean up generated files or use a temporary storage directory.
Testing JSON Views
Send JSON:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
Inspect JSON:
⧉
1 2 3 4 5 6 | |
For Django REST Framework projects, its APITestCase and APIClient provide API-specific conveniences, but the same arrange-act-assert principles apply.
Testing Redirects
Use assertRedirects():
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
This checks the redirect URL and response status.
Testing Messages
Suppose a view adds a success message:
⧉
1 2 3 4 5 6 7 | |
Test it:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
The Test Database
When database tests run, Django creates a separate test database.
Conceptually:
⧉
1 2 3 4 5 | |
Django applies migrations to the test database and runs tests against it. Test data is then cleaned up according to the test class being used. The database test utilities create a test database and run migrations before executing the suite.
Never rely on records in the development database.
Create every record required by the test.
Test Isolation
A test should not change the outcome of another test.
This means tests should not depend on:
- execution order
- leftover database records
- global mutable state
- files created by another test
- a previous login
- a previous cache value
- an external service’s current state
Django provides database and test-client isolation, but application-level global state may still need explicit cleanup.
Unit Tests and Integration Tests
The phrase “unit test” is often used broadly in Django projects.
A narrow unit test checks one isolated function:
⧉
1 2 3 4 5 6 | |
A view test may involve:
- URL routing
- middleware
- database queries
- templates
- authentication
That is closer to an integration test because several components work together.
Both kinds are valuable.
A practical Django test suite often contains:
⧉
1 2 3 4 5 6 7 8 | |
The goal is not to force every test into one category. The goal is to test behavior at the most useful level.
What to Test
Prioritize behavior that contains risk or business value.
Good candidates include:
- custom model methods
- form validation
- permission rules
- authentication requirements
- object ownership
- important database constraints
- calculations
- status transitions
- redirects
- API validation
- bug fixes
- custom queryset logic
Do not spend large amounts of time testing Django’s own framework behavior.
For example, this test provides little value:
⧉
1 2 3 4 5 6 7 8 9 | |
It mainly checks that Django’s CharField works.
This test is more valuable:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
It checks custom application behavior.
Test Boundaries and Edge Cases
Do not test only the normal case.
If a title must contain between 5 and 200 characters, useful cases include:
⧉
1 2 3 4 5 | |
For a numeric calculation, consider:
⧉
1 2 3 4 5 | |
For permissions, consider:
⧉
1 2 3 4 5 6 7 8 | |
Bugs often exist at boundaries rather than in the most common case.
One Behavior per Test
A test should usually focus on one behavior.
Less focused:
⧉
1 2 3 4 5 6 7 8 | |
More focused:
⧉
1 2 | |
⧉
1 2 | |
⧉
1 2 | |
⧉
1 2 | |
Focused tests make failures easier to diagnose.
Avoid Excessive Assertions
Several assertions are reasonable when they describe one behavior.
For example:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | |
All assertions describe the successful creation behavior.
Avoid combining unrelated requirements merely to reduce the number of test methods.
Do Not Repeat Production Logic in Tests
Suppose production code calculates:
⧉
1 | |
A weak test may repeat the same implementation:
⧉
1 2 3 4 5 6 | |
If the requirement is known, state it directly:
⧉
1 2 3 4 5 6 7 8 9 | |
A test should verify behavior independently rather than reproduce the same algorithm.
Bug-Fix Tests
When fixing a bug:
- Write a test that reproduces the bug.
- Run it and confirm that it fails.
- Fix the code.
- Run the test and confirm that it passes.
- Keep the test to prevent the bug from returning.
Example bug:
⧉
1 | |
Regression test:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
This is called a regression test because it prevents previously fixed behavior from regressing.
Test Coverage
Coverage tools measure which lines or branches ran during tests.
A common tool is installed with:
⧉
1 | |
Run tests through it:
⧉
1 | |
View the report:
⧉
1 | |
Generate an HTML report:
⧉
1 | |
Coverage can identify untested code, but a high percentage does not guarantee high-quality tests.
A test may execute a line without making a meaningful assertion about its behavior.
Use coverage to locate gaps, not as the only measure of test quality.
Organizing a Test Suite
A practical structure is:
⧉
1 2 3 4 5 6 7 8 9 10 | |
Possible responsibilities:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
Do not create many tiny files before the app needs them.
A small app may be perfectly clear with one tests.py.
Common Beginner Mistakes
Not Creating Test Data
Tests do not use the normal development database.
Create the required records inside the test:
⧉
1 2 3 4 | |
Depending on Test Order
Each test must prepare its own state.
Never assume another test ran first.
Testing Only Status Code 200
This is incomplete:
⧉
1 2 3 4 | |
Also consider checking:
- the template
- context data
- visible content
- hidden content
- database changes
- permissions
- redirects
Testing Only the Successful Case
Also test:
- invalid data
- missing data
- anonymous access
- unauthorized access
- missing objects
- duplicate values
- boundary values
Using Plain-Text Password Assignment
Avoid:
⧉
1 2 3 4 | |
Use:
⧉
1 2 3 4 | |
Hard-Coding URLs
Avoid:
⧉
1 | |
Prefer:
⧉
1 2 3 | |
Making Real External Requests
Mock or replace external dependencies.
Tests should remain fast, repeatable, and independent of network availability.
Overusing Mocks
Mock external boundaries, not every internal method.
Too many mocks can make a test pass even when the real components no longer work together.
Testing Implementation Details
Avoid checking internal calls unless the call itself is part of the required behavior.
Prefer testing observable outcomes:
- returned value
- database change
- response
- sent message
- permission decision
Writing Huge Tests
Split unrelated behaviors into separate test methods.
A failing test should clearly indicate what requirement broke.
Ignoring Failing Tests
A failing test means one of three things:
- the application is wrong
- the test is wrong
- the requirement changed
Investigate the cause rather than removing the test merely to make the suite pass.
A Complete Example
Model:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
View:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
URL:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 | |
Tests:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | |
This test class checks:
- the normal successful case
- unpublished content
- missing content
- response status
- template selection
- rendered content
Recommended Testing Workflow
A practical workflow is:
- Identify one behavior.
- Create the minimum required test data.
- Perform the action.
- Assert the expected result.
- Run the focused test.
- Confirm the test fails when the behavior is broken.
- Make the implementation pass.
- Run the entire test suite.
- Refactor while keeping the tests green.
Useful commands include:
⧉
1 | |
⧉
1 | |
⧉
1 2 | |
⧉
1 | |
⧉
1 | |
What to Test First
When adding tests to an existing application, begin with:
- Important business calculations
- Authentication requirements
- Permission and ownership rules
- Form and API validation
- Critical model methods
- Important create, update, and delete workflows
- Previously reported bugs
- High-risk edge cases
Do not begin by trying to test every line.
Start with behavior whose failure would matter most.
Mini Reference
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | |
Basic model test:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Basic view test:
⧉
1 2 3 4 5 6 7 8 9 | |
Run all tests:
⧉
1 | |
Unit testing in Django provides a structured way to verify that application behavior remains correct.
The main ideas are:
- tests arrange data, perform an action, and assert a result
- Django’s testing tools build on Python’s
unittestframework SimpleTestCaseis useful without a databaseTestCaseis the standard choice for most database-backed tests- Django creates an isolated test database
- the test client simulates requests without starting a server
- models, forms, views, templates, authentication, permissions, and email can all be tested
- each test should be independent
- tests should focus on important behavior and edge cases
- bug fixes should include regression tests
- coverage is useful, but meaningful assertions matter more than percentages
Start with small tests around the most important parts of the application. As the project grows, the test suite becomes both documentation of expected behavior and protection against accidental changes.
Join the Newsletter
Practical insights on Django, backend systems, deployment, architecture, and real-world development — delivered without noise.
Get updates when new guides, learning paths, cheat sheets, and field notes are published.
No spam. Unsubscribe anytime.
There is no third-party involved so don't worry - we won't share your details with anyone.