skip to content

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

level: seniorimportance: nice to knowfreq 24%

answer

  1. flush empties migration data
  2. serialize once, reload per test
  3. only aliases that ask for it
  4. about three times slower

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.

solid answer

~40 s

A `TransactionTestCase` resets state by running `flush` after each test, which empties every table, including rows inserted by data migrations such as seat categories or default venues. With `serialized_rollback = True`, the runner serializes the test database right after creating it (only for the aliases such classes use), and before each test of that class Django deserializes that snapshot back in. The flush then skips the `post_migrate` signal so the data is not created twice. Django's docs put the cost at roughly three times slower for that class, and `TEST_NON_SERIALIZED_APPS` can exclude apps from the snapshot. You need it when transactional tests depend on migration-created data, or for reusable apps that must run on databases without transactions. Since Django 5.2 the reloaded data is already there in `setUpClass()`.

code

python · 12 lines
python
from django.test import TransactionTestCase

from tickets.models import SeatCategory


class CategoryPricingCommitTests(TransactionTestCase):
    # SeatCategory rows come from a data migration; without this, the flush
    # after an earlier transactional test leaves the table empty.
    serialized_rollback = True

    def test_stalls_category_exists(self):
        self.assertTrue(SeatCategory.objects.filter(code="STALLS").exists())

go deeper

for a junior

Know that TransactionTestCase empties the tables after each test and that a flag exists to bring migration data back.

for a middle

Explain the snapshot taken after migrations, reloaded before each test, and why post_migrate is skipped on flush.

for a senior

Diagnose order-dependent DoesNotExist failures in transactional tests and choose between serialized_rollback and explicit per-test data.

for a principal

Limit reliance on migration-seeded data in tests, since it couples the suite to migration history and slows transactional classes.

## The problem Many projects seed reference data in **data migrations**: seat categories, default currencies, a public venue. After `migrate` has run on the test database, those rows exist, and every `TestCase` sees them because it only rolls back its own changes. A `TransactionTestCase` is different. After each test it runs the **`flush`** management command on each database in its `databases` attribute, which truncates every table. The first transactional test therefore wipes the migration data, and every later transactional test runs against empty reference tables. A test that looks up `SeatCategory.objects.get(code="STALLS")` fails with `DoesNotExist` only when run after another transactional test, which makes the failure look random. The runner mitigates part of this by running **all `TestCase` classes first**, so they always see migration data. The problem is confined to the transactional group. ## What `serialized_rollback = True` does 1. **At database creation**, the test runner collects the aliases used by test classes that set `serialized_rollback = True` and serializes those test databases' contents to a string held on the connection, right after migrations ran. 2. **Before each test** of such a class, Django **deserializes** that string back into the database, so the migration data is present again. 3. **At flush time**, Django **inhibits the `post_migrate` signal**, because the snapshot already contains what `post_migrate` handlers (content types, permissions) would recreate, and running both would duplicate them. ## The costs - **Time**: Django's documentation estimates the class runs about **3x slower**, because every test reloads the snapshot on top of the flush. - **Snapshot size**: every serialized app's data is included; list apps to skip in the `TEST_NON_SERIALIZED_APPS` setting (default an empty list). - **Serialization rules**: the data must round-trip through Django's serializers, which is normally true for plain model data. ## When to use it - Transactional tests that **need migration-created data**: reference tables, groups and permissions created by a data migration. - **Reusable apps** tested against a database without transaction support (such as MySQL's MyISAM engine), where even `TestCase` falls back to flushing. - `LiveServerTestCase` and `StaticLiveServerTestCase` classes, which inherit the flush behaviour, when pages depend on seeded data. When **not** to use it: - if only a few rows are needed, create them in `setUp()`; that is faster and clearer; - in `TestCase` on a transactional database, where it has no effect on the normal rollback path. ## Related knobs on `TransactionTestCase` | Attribute | Default | Effect | |---|---|---| | `serialized_rollback` | `False` | reload the post-migration snapshot before each test | | `databases` | `{"default"}` | which databases are flushed and may be queried; `"__all__"` for all | | `reset_sequences` | `False` | reset primary-key sequences before each test (not allowed on `TestCase`, which raises `TypeError`) | | `fixtures` | `None` | fixture files loaded before each test into each database in `databases` | | `available_apps` | `None` | limit the app registry for the class, making flushes cheaper; an advanced option | ## A version note Since **Django 5.2**, data loaded from `fixtures` and from the serialized snapshot is already available during `TransactionTestCase.setUpClass()`, so class-level setup can rely on it. On 5.1 and earlier it appeared only once each test's setup ran. ## Diagnosing the symptom 1. Run the failing test alone; if it passes, suspect order. 2. Run its whole module, then the suite with `--reverse`; a failure that appears only after other transactional classes points at a flush. 3. Check whether the missing rows come from a data migration. 4. Decide: `serialized_rollback = True` if many tests need that data, or explicit creation in `setUp()` if only a few do.

  • Why does a Django TransactionTestCase test fail with DoesNotExist only when the whole suite runs?
    An earlier transactional test flushed every table after it ran, deleting rows that a data migration created. Run alone, the test sees the freshly migrated database; in the suite, it follows a flush. Set `serialized_rollback = True` on the class, or create the rows the test needs in `setUp()`.

saying these in an interview costs you the question

  • TransactionTestCase keeps rows created by data migrations
  • serialized_rollback makes TransactionTestCase as fast as TestCase
  • Every test database is serialized whether or not a class asks for it
  • serialized_rollback replaces flushing with a transaction rollback
  • Rows from migrations are missing in TestCase tests too