SimpleTestCase, TestCase, and TransactionTestCase
coreintermediateThe three classes differ in how they handle the database, and choosing wrongly is either slow or subtly broken. `SimpleTestCase` **forbids** database access — touching it raises, which is exactly what you want for a template filter or a pure function. `TestCase` wraps each test in a transaction and rolls it back afterwards, which makes it fast and is the right default. `TransactionTestCase` genuinely commits and then truncates the tables between tests, which is much slower but is the only one that can test anything involving real commits — `transaction.on_commit()` callbacks, `select_for_update()`, or code that expects another connection to see its writes.
Think of it as
`TestCase`'s speed comes from never committing, and that single fact explains every case where it gives the wrong answer. Your code under test runs inside a transaction that will be rolled back, so anything whose behaviour depends on a commit does not happen: an `on_commit` callback never fires, a second database connection cannot see your writes, and `select_for_update()` has no competing transaction to block against. None of that produces an error — the test simply passes without exercising the thing you meant to test, which is worse than failing. So the rule is not "use `TransactionTestCase` when you have database code", it is "use it when the *commit itself* is part of what you are testing", and that is a much smaller set. `setUpTestData` is the other piece worth knowing: it runs once per class inside an outer transaction, so shared fixtures are created a single time rather than per test — but the objects are shared between tests, which means mutating one in a test can leak into the next unless you re-fetch. On `SimpleTestCase`, treat the database ban as a design signal rather than a limitation: if a test you expected to be pure suddenly needs the database, something has reached further than it should.
What we're doing: Pick the cheapest class that can actually observe what each test is about.
- 1–2
- `SimpleTestCase` turns "this should not need the database" into an enforced rule — if the test starts failing with a database error, the code under test grew a dependency it should not have.
- 9–11
- `setUpTestData` creates the customer once per class rather than once per test. The objects are shared, so a test that mutates `cls.customer` must re-fetch rather than assume.
- 19–21
- `captureOnCommitCallbacks(execute=True)` is what makes `on_commit` testable without paying for `TransactionTestCase` — it runs the pending callbacks and lets you assert on their effect.
- 24–28
- The one test that genuinely needs real commits, because two connections have to contend for a lock. Everything else stays on the fast class.
Why this works: Three classes for three different needs: a database ban that catches accidental coupling, fast rollback isolation for the bulk of the suite, and real commits only where the commit is the thing being tested.
Asserting on an `on_commit` side effect inside a `TestCase`
Wrong
Better
What you see: The assertion fails with `0 != 1` even though the code is correct in production — or, worse, someone "fixes" it by asserting `0`, which makes the test pass while proving the opposite of what was intended.
Why: `TestCase` wraps each test in a transaction it rolls back, so `COMMIT` never happens and callbacks registered with `transaction.on_commit()` are never invoked. The failure looks like a bug in the code rather than in the test, which is how the wrong fix gets applied. `captureOnCommitCallbacks(execute=True)` runs them explicitly and keeps the test on the fast class.
- TestCase — never commits
- Each test runs inside a transaction that is rolled back.
- Fast, and isolated without truncating anything.
- on_commit callbacks never fire — the assertion silently passes.
- A second connection cannot see the writes, so locking tests prove nothing.
- Right for almost every test that is not about committing.
- TransactionTestCase — really commits
- Writes are committed, then tables are truncated between tests.
- on_commit callbacks fire exactly as they do in production.
- A second connection sees the data, so select_for_update is testable.
- Sequences reset, so tests assuming a specific pk break.
- Noticeably slower — reserve it for tests about commits.
Which class, and what it costs
Together
Remember: `SimpleTestCase` forbids the database, which turns "this should be pure" into an enforced rule. `TestCase` wraps each test in a rolled-back transaction — fast, isolated, and the right default — but because it never commits, `on_commit` callbacks do not fire and other connections cannot see your writes, so use `captureOnCommitCallbacks(execute=True)` rather than switching classes. `TransactionTestCase` really commits and truncates, and is only worth its cost when the commit itself is what you are testing.
See also: the test client and what to test · pytest django and fixtures · on commit and transaction timing

