skip to content

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