skip to content

Test Toolkit

Django's django.test package, from TestCase and the test Client to the throwaway test database and settings overrides, plus the plugin for the test runner. Interviewers probe isolation and speed.

part ofDjangooverview, primer and where to startread it →
on this pageshow

explore

questions

26

In Django's test framework, how do SimpleTestCase, TransactionTestCase and TestCase differ, and which one should most tests use?

level: juniorimportance: must knowfreq 66%

answer

  1. no database, flush, rollback
  2. databases attribute gates queries
  3. two nested atomic blocks
  4. runner puts TestCase first

basics

~20 s

SimpleTestCase forbids database queries. TransactionTestCase lets code commit and then truncates every table after each test. TestCase wraps each test in a transaction rolled back at the end, which is much faster, so most database tests use TestCase.

solid answer

~40 s

All three live in `django.test`. `SimpleTestCase` adds Django's assertions, the test client and settings overrides, but its `databases` attribute is an empty set, so any query raises `DatabaseOperationForbidden`. `TransactionTestCase` allows the `default` database; code may really commit, and after each test Django **flushes** the tables, which is slow and also wipes data created by migrations. `TestCase` wraps the class in one `atomic` block and each test in another, and rolls the inner one back, so the reset is a cheap rollback and `setUpTestData()` can build shared data once per class. It also checks deferrable constraints at the end of each test. Use `TestCase` by default, `SimpleTestCase` for code with no database, and `TransactionTestCase` only to test real commit behaviour such as `on_commit` callbacks or `select_for_update`.

code

python · 20 lines
python
from django.test import SimpleTestCase, TestCase

from shop.models import Event
from shop.pricing import format_price


class FormatPriceTests(SimpleTestCase):
    def test_two_decimals(self):
        self.assertEqual(format_price(1250), "12.50")  # no database needed


class EventTests(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.event = Event.objects.create(name="Jazz night", capacity=120)

    def test_sell_out(self):
        self.event.capacity = 0
        self.event.save()  # rolled back after this test
        self.assertTrue(Event.objects.get(pk=self.event.pk).is_sold_out)

go deeper

for a junior

Know the three classes: no database, flush after each test, rollback after each test, and that TestCase is the default for model code.

for a middle

Explain the two nested atomic blocks, why rollback is faster than flush, what the databases attribute does, and the runner's ordering.

for a senior

Recognise behaviour TestCase hides, such as on_commit and select_for_update outside a transaction, and keep TransactionTestCase use small and deliberate.

for a principal

Set suite conventions: default base classes, a budget for transactional tests, and reviews that question every TransactionTestCase for cost and necessity.

## The hierarchy Django's test case classes extend Python's `unittest.TestCase` in a chain, each adding database behaviour to the one above: 1. **`SimpleTestCase`**, a subclass of `unittest.TestCase`; 2. **`TransactionTestCase`**, a subclass of `SimpleTestCase`; 3. **`TestCase`**, a subclass of `TransactionTestCase`; 4. **`LiveServerTestCase`**, also a subclass of `TransactionTestCase`, which adds a running HTTP server. The classes differ mainly in two things: **which databases a test may touch**, and **how the database is put back** after each test. | Class | `databases` default | Database reset | Can the code under test commit? | Speed | |---|---|---|---|---| | `SimpleTestCase` | `set()`: no queries allowed | none needed | no database | fastest | | `TransactionTestCase` | `{"default"}` | **flush** (truncate all tables) **after** each test | yes | slowest | | `TestCase` | `{"default"}` | **rollback** of a per-test transaction | no, everything stays inside a transaction | fast | ## `SimpleTestCase` It gives you Django's extra assertions (`assertContains`, `assertRedirects`, `assertHTMLEqual` and the rest), `self.client`, and settings overrides, with no database. Its `databases` attribute is an empty set, and Django replaces the connection's methods so that any query **raises `DatabaseOperationForbidden`**, a subclass of `AssertionError`, with a message telling you to use `TestCase` or `TransactionTestCase` or to add the alias to `databases`. Since Django 5.1 it also refuses database connections opened in threads. This guard exists because a `SimpleTestCase` test runs without a transaction, so any write would leak into other tests. ## `TransactionTestCase` It behaves like production: code can call `commit` and `rollback` and you can observe the effects. After each test, Django runs the `flush` command on each database in `databases`, which truncates every table. Consequences: - it is slow, especially with many tables; - rows created by **data migrations** are gone after the first flush unless you set `serialized_rollback = True`; - `reset_sequences = True` can reset primary-key sequences before each test (not allowed on `TestCase`). ## `TestCase` It wraps the tests in **two nested `atomic` blocks**: one opened in `setUpClass()` for the whole class, and one opened before each test. At the end of each test the inner one is rolled back, and at the end of the class the outer one is. This gives: - a cheap reset, because a rollback discards the changes without touching every table; - **`setUpTestData()`**, a class method that runs once inside the class-level transaction, so shared data is built once instead of before every test; - a check of **deferrable constraints** at the end of each test, on backends that can defer them, so a broken foreign key fails the test that caused it. On a database without transaction support, `TestCase` quietly behaves like `TransactionTestCase`, and `setUpTestData()` runs before every test. ## What `TestCase` cannot test Because the test's code never leaves a transaction, some behaviour is invisible: - `transaction.on_commit()` callbacks never run on their own, since nothing commits; - a `select_for_update()` that would fail outside a transaction does not fail, because there always is one; - behaviour that depends on another connection seeing committed data, such as a server running in another thread. For those, use `TransactionTestCase` (or, for `on_commit`, a helper that captures the callbacks inside `TestCase`). ## Order of execution Django's test runner reorders tests so that every `TestCase` starts from a clean database: **all `TestCase` subclasses run first**, then other Django test classes (including `TransactionTestCase`), then plain `unittest.TestCase` tests. The `--shuffle` and `--reverse` options randomise or reverse order only inside these groups. ## A rule of thumb - Pure functions, forms without models, template tags: `SimpleTestCase`. - Anything that reads or writes models: `TestCase`. - Commit semantics, locks, on-commit side effects, live servers: `TransactionTestCase` or `LiveServerTestCase`, kept to as few tests as possible.

  • What happens when a Django SimpleTestCase test runs a query, and how can you allow it?
    Django raises `DatabaseOperationForbidden`, an `AssertionError`, whose message says to subclass `TestCase` or `TransactionTestCase` or add the alias to `databases`. Setting `databases = "__all__"` (or a set of aliases) on the class allows queries, but nothing then resets the data, so writes leak into other tests. Switching to `TestCase` is almost always the better fix.
  • Why does Django's test runner run TestCase classes before TransactionTestCase classes?
    A `TestCase` relies on starting from a clean database and only rolls back its own changes. A `TransactionTestCase` flushes tables after each test, which also removes data from migrations. Running all `TestCase` classes first guarantees they see the database as migrations left it; the transactional tests, which tolerate or restore that state themselves, come afterwards.

TestCase is like editing a document with undo history and pressing undo at the end of every test; TransactionTestCase saves the file for real and then deletes and recreates it before the next person uses it.

saying these in an interview costs you the question

  • TestCase truncates every table after each test
  • SimpleTestCase silently allows reads from the default database
  • TransactionTestCase is the faster choice because it skips transactions
  • on_commit callbacks run normally inside a Django TestCase
  • The runner runs TestCase subclasses last
open as a page

In Django testing, what is the difference between the test Client and RequestFactory, and when would you reach for each?

level: juniorimportance: must knowfreq 62%

basics

~20 s

Django's test Client runs a request through URL routing, every middleware and template rendering, like an in-process browser. RequestFactory only builds a request object you pass to one view yourself, with no middleware, so you set request.user and session by hand.

open as a page

When you run Django's manage.py test, which database do the tests use, and what happens to it before and after the run?

level: juniorimportance: must knowfreq 58%

basics

~20 s

Django's test runner creates a separate database named 'test_' plus each NAME in DATABASES (in memory for SQLite), runs migrate into it, runs the tests, and destroys it at the end unless --keepdb is passed.

open as a page

In a Django TestCase, how do you assert that a sign-up view sends exactly one welcome email from an overridden DEFAULT_FROM_EMAIL?

level: juniorimportance: must knowfreq 60%

basics

~10 s

Django's test runner swaps in the locmem email backend, which appends every sent message to django.core.mail.outbox. Post to the view under @override_settings(DEFAULT_FROM_EMAIL=...), then assert len(mail.outbox) == 1 and inspect that message.

open as a page

With pytest-django, why does a plain test function that queries a Django model fail with 'Database access not allowed', and how do you fix it?

level: juniorimportance: must knowfreq 55%

basics

~10 s

pytest-django blocks database access unless a test opts in, so an unmarked query raises RuntimeError. Add @pytest.mark.django_db or request the db fixture; the test then runs in a transaction that is rolled back afterwards.

open as a page

In Django tests, how do override_settings and modify_settings differ, and when do you reach for each?

level: middleimportance: must knowfreq 55%

basics

~20 s

override_settings replaces whole setting values for a test or block; modify_settings edits list settings such as MIDDLEWARE or INSTALLED_APPS by appending, prepending or removing items. Both restore the originals when the test or block ends.

open as a page

In Django tests, what do assertContains and assertTemplateUsed check that a plain substring search on response.content does not?

level: juniorimportance: should knowfreq 40%

basics

~20 s

assertContains also asserts the status code (200 by default), decodes the body, can count occurrences and, with html=True, compares parsed HTML. assertTemplateUsed checks which templates the test Client recorded as rendered, which a content search cannot see.

open as a page

In a Django test, how do you verify that an anonymous visitor to a login-protected dashboard is redirected to the login page?

level: juniorimportance: should knowfreq 46%

basics

~10 s

Request the dashboard with the test Client and call assertRedirects(response, '/accounts/login/?next=/dashboard/'). It checks the 302, the target URL, and that the target loads with 200; follow=True with redirect_chain shows every hop.

open as a page

Why does Django's LiveServerTestCase inherit from TransactionTestCase rather than TestCase, and what does it give a browser test?

level: middleimportance: should knowfreq 34%

basics

~20 s

LiveServerTestCase starts a real Django server in a background thread for browser tests. That thread uses its own database connection, which cannot see a TestCase's uncommitted data, so the class commits and flushes like TransactionTestCase.

open as a page

What does Django's TestCase.setUpTestData give you over setUp, and why are its class attributes deep-copied for each test?

level: middleimportance: should knowfreq 48%

basics

~20 s

setUpTestData runs once per TestCase class inside a class-level transaction, so shared rows are created once instead of before every test. Its class attributes are deep-copied per test so in-memory changes in one test do not leak into the next.

open as a page

In Django's test Client, how does client.login() differ from client.force_login(), and which should most view tests use?

level: middleimportance: should knowfreq 50%

basics

~20 s

client.login() authenticates credentials through AUTHENTICATION_BACKENDS, paying the password hash cost, and returns True or False. client.force_login(user) skips authentication and writes the session directly, so it is faster and is the usual choice when how the user logged in does not matter.

open as a page

In Django tests, how does seeding data with TestCase.fixtures compare with building it through model factories, and which do teams prefer?

level: middleimportance: should knowfreq 46%

basics

~20 s

TestCase.fixtures loads serialized files through loaddata, once per class, with raw saves that skip custom save() logic and drift as models change. Model factories build objects in Python per test, so most teams prefer factories and keep fixtures for small static reference data.

open as a page

What does Django's test --keepdb option save, and when can a kept test database give you misleading results?

level: middleimportance: should knowfreq 40%

basics

~20 s

--keepdb skips creating and destroying the test database, only applying unapplied migrations. It misleads when an already-applied migration was edited, when switching branches leaves a different schema, or when MIGRATE is False and models changed.

open as a page

Why can a value stored in Django's cache by one test still be there in the next test, and how do you stop it?

level: middleimportance: should knowfreq 40%

basics

~20 s

Django resets the database and the mail outbox between tests but never the cache backends, and the default LocMemCache lives for the whole test process. Call cache.clear() per test or override CACHES with a dedicated backend.

open as a page

When porting a Django TestCase suite to pytest-django, what replaces self.client, override_settings, assertNumQueries and assertContains in plain test functions?

level: middleimportance: should knowfreq 45%

basics

~20 s

Fixtures replace the class machinery: client, admin_client and rf for requests, settings for overrides, django_assert_num_queries for query counts, mailoutbox for mail, and pytest_django.asserts for assertContains and friends. Old TestCase classes keep running, so port gradually.

open as a page

In pytest-django, what does @pytest.mark.django_db(transaction=True) change compared with the plain marker, and when is it worth the cost?

level: middleimportance: should knowfreq 42%

basics

~20 s

transaction=True runs the test like Django's TransactionTestCase: real commits instead of a rolled-back transaction, with tables flushed afterwards. It is slower, so use it only when the test needs committed data, such as on_commit behaviour or other connections.

open as a page

A Django ticket shop issues tickets in transaction.on_commit; why does its TestCase test never see the tickets, and when do you switch to TransactionTestCase?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Django's TestCase runs every test inside transactions that are rolled back, so nothing commits and on_commit callbacks are discarded. TransactionTestCase lets the code commit, so the callbacks run, at the price of flushing tables after each test.

open as a page

A Django 5.1+ project protects its dashboard with LoginRequiredMiddleware; why do RequestFactory tests prove nothing about that protection, and how should it be tested?

level: seniorimportance: should knowfreq 32%

basics

~20 s

LoginRequiredMiddleware enforces login in process_view, and a RequestFactory request never passes through middleware, so the view runs for anyone. Protection must be tested with the test Client: an anonymous get() of the dashboard URL, then assertRedirects to LOGIN_URL with next.

open as a page

How does Django's test --parallel option split the suite and the database, and what makes a suite unsafe to run with it?

level: seniorimportance: should knowfreq 36%

basics

~20 s

--parallel runs test case classes in several processes, one per CPU core by default, and gives each process its own clone of the test database. Tests are unsafe when they share anything else: files, an external cache, ports or order.

open as a page

A Django test suite now takes 20 minutes; how do you find where the time goes and cut the database-related share of it?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Measure first with --timing and --durations, then attack what dominates: --keepdb for setup, --parallel for idle cores, setUpTestData and factories instead of per-test data, a fast password hasher in test settings, and TestCase over TransactionTestCase where possible.

open as a page

What exactly does Django's assertNumQueries count, and why can a view's count differ between a TestCase and production?

level: seniorimportance: should knowfreq 45%

basics

~20 s

assertNumQueries counts every SQL statement run on one database alias inside the call or block and requires an exact match. In a TestCase, savepoints from nested atomic blocks and warm or cold ContentType caches can shift that count.

open as a page

Why do transaction.on_commit callbacks never fire inside a Django TestCase, and how does captureOnCommitCallbacks(execute=True) let you test them?

level: seniorimportance: should knowfreq 45%

basics

~20 s

TestCase runs each test inside an atomic block that is rolled back, so the outer transaction never commits and on_commit callbacks are discarded. Wrapping the code in self.captureOnCommitCallbacks(execute=True) collects those callbacks and calls them when the block exits.

open as a page

In pytest-django, why does seeding shared rows once per session behave differently from per-test fixtures, and what breaks when transactional tests run?

level: seniorimportance: should knowfreq 28%

basics

~20 s

Rows seeded in a session-level django_db_setup override are committed outside any test transaction, so rollbacks never remove them. The first transactional test's table flush deletes them, and with --reuse-db they persist into the next run.

open as a page

In Django, when do you need AsyncClient or AsyncRequestFactory instead of Client and RequestFactory, and what changes in how you call them?

level: middleimportance: nice to knowfreq 24%

basics

~20 s

Use AsyncClient inside async def tests, or to exercise Django's ASGI request path; each request method must be awaited and the view gets an ASGIRequest. AsyncRequestFactory builds ASGIRequest objects with synchronous methods, for calling an async view directly.

open as a page

How does pytest-django decide which Django settings module to load, and what is the precedence between --ds, DJANGO_SETTINGS_MODULE and pytest.ini?

level: middleimportance: nice to knowfreq 26%

basics

~10 s

pytest-django takes the settings module from --ds first, then the DJANGO_SETTINGS_MODULE environment variable, then the DJANGO_SETTINGS_MODULE key in pytest's config file. Putting --ds in addopts makes the file win.

open as a page

What does serialized_rollback = True do on a Django TransactionTestCase, and when do you need it?

level: seniorimportance: nice to knowfreq 24%

basics

~20 s

serialized_rollback = True makes Django reload a serialized snapshot of the freshly migrated test database before each test, restoring rows that data migrations created and that TransactionTestCase's flush deleted. It is slow, so use it only when tests need that data.

open as a page