skip to content

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%

answer

  1. once per class, not per test
  2. class-level atomic block
  3. in-memory changes would leak
  4. deepcopy with a shared memo

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.

solid answer

~40 s

`setUpTestData()` is a class method on `django.test.TestCase`. It runs once, inside the class-wide `atomic` block, and the objects it creates are visible to every test; each test's own changes to the database are rolled back by the per-test transaction. That is far cheaper than `setUp()`, which rebuilds the data before every test. The catch is Python objects: a test that edits `self.event.capacity` without saving would change the shared instance for later tests. So Django wraps each attribute assigned in `setUpTestData()` in a descriptor that returns a `copy.deepcopy` the first time a test reads it, with one memo per test so related objects stay linked. Attributes must therefore be deep-copyable. On a database without transactions, `setUpTestData()` runs before every test and the speed gain disappears.

code

python · 17 lines
python
from django.test import TestCase

from tickets.models import Event, Seat


class SeatMapTests(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.event = Event.objects.create(name="Jazz night", capacity=200)
        Seat.objects.bulk_create(Seat(event=cls.event, number=n) for n in range(1, 201))

    def test_free_seats(self):
        self.assertEqual(self.event.seats.filter(sold=False).count(), 200)

    def test_capacity_edit_does_not_leak(self):
        self.event.capacity = 0  # changes this test's deep copy only
        self.assertEqual(Event.objects.get(pk=self.event.pk).capacity, 200)

go deeper

for a junior

Remember that setUpTestData is a classmethod that creates shared test data once, and that setUp runs before every test.

for a middle

Explain the class-level and per-test atomic blocks, and why in-memory objects need deep copies even though database writes are rolled back.

for a senior

Spot suites slowed by setUp-heavy data, move shared worlds into setUpTestData, and keep non-copyable objects out of it.

for a principal

Treat test data construction as a performance budget: shared fixtures per class, factories for variation, and measurement of suite time.

## The problem it solves In a `TestCase`, every test runs inside a transaction that is rolled back afterwards. If a class has 30 tests and each needs an event, a venue and 200 seats, creating them in `setUp()` means 30 rounds of inserts. `setUpTestData()` creates them **once per class** instead. ## How it works `django.test.TestCase.setUpClass()` does the following: 1. opens a **class-level `atomic` block** on each database in `databases`; 2. loads any `fixtures` listed on the class; 3. calls **`setUpTestData()`**, a `@classmethod` you override; 4. wraps every class attribute that `setUpTestData()` assigned or replaced in a `TestData` descriptor. Then, for each test, `TestCase` opens a second, per-test `atomic` block (a savepoint inside the class transaction) and rolls it back at the end. At the end of the class, the class-level block is rolled back too, so nothing persists. | | `setUp()` | `setUpTestData()` | |---|---|---| | Kind of method | instance method | `@classmethod` | | Runs | before every test | once per class | | Data lives in | the per-test transaction | the class transaction, visible to all tests | | Database writes by a test | rolled back | rolled back (the class data stays) | | In-memory objects | fresh each test | deep copy of the class objects each test | ## Why deep copies The database side is safe: the per-test rollback undoes every write. The **Python objects** are another matter. Class attributes are shared by all test instances. Without protection: - a test sets `self.event.capacity = 0` without saving, and the next test sees `0` in memory while the database still says 120; - a test calls `self.order.refresh_from_db()` after changing the row, and later tests inherit the refreshed cache; - a test appends to a list stored on the class. Django therefore makes each attribute a **`TestData` descriptor**. The first time a test instance reads `self.event`, the descriptor returns `copy.deepcopy()` of the original and stores the copy on that instance. All copies made for one test share a **memo**, so if `self.order` refers to `self.customer`, the order's cached customer and `self.customer` are the same copy, keeping relationships consistent. Rules that follow: - objects assigned in `setUpTestData()` **must support `deepcopy`**; model instances, lists and dicts do, while things such as open files, locks or some client objects do not, and will fail when a test first reads them; - anything created in `setUpTestData()` but not assigned to the class is still in the database, only without a handy attribute; - `setUpClass()` overrides must call `super()` first, or the class transaction and the descriptor wrapping never happen. ## When it does not help - On a database **without transaction support**, `TestCase` behaves like `TransactionTestCase` and calls `setUpTestData()` before every test, so the gain is lost. - In a `TransactionTestCase`, `setUpTestData()` is not called at all; it belongs to `TestCase`. - Data that tests must mutate heavily, or objects that cannot be copied, belong in `setUp()`. ## Practical pattern Build the expensive, shared world in `setUpTestData()` (event, venue, seats, a user), and do per-test variations in the test itself. Keep factories or helper functions for the variations, and let the per-test rollback clean up after them. ## Common mistakes - **Forgetting `@classmethod`**: Django calls `cls.setUpTestData()` on the class, so a version written as an instance method fails with a `TypeError` about the missing argument. - **Mutating class data on purpose** and expecting later tests to see it: each test gets its own copy, so cross-test state through class attributes does not work, which is the point. - **Assuming the copy re-reads the database**: the copy comes from the in-memory original, so call `refresh_from_db()` on the test's copy when you need the current row. - **Heavy objects on the class**: deep-copying a large object graph on every test costs time; store identifiers or small objects and query when needed.

  • In a Django TestCase, what goes wrong if setUpTestData assigns an object that cannot be deep-copied?
    The assignment itself works, but the first time a test reads the attribute, the `TestData` descriptor calls `copy.deepcopy()` on it and the copy fails with an error from the object's type (for example, a file object or a lock). Keep such objects out of `setUpTestData()`: create them in `setUp()`, or store only plain values and model instances on the class.

saying these in an interview costs you the question

  • setUpTestData runs before every test like setUp
  • Objects from setUpTestData are shared by reference between tests
  • Rows created in setUpTestData persist into the next test class
  • setUpTestData is also called in TransactionTestCase
  • Deep copies mean the database rows are duplicated per test