Load Django fixtures once per class, not once per test
Django fixtures (loaddata) save every object one at a time. Load them once per TestCase class or pytest session instead of before every test, or use factories.
Django fixtures are JSON, YAML or XML files of serialized rows that loaddata puts into the test database. They're convenient for reference data, but loading them is slow, and depending on how a test uses them they're loaded once per class, once per test, or once per test in every one of dozens of classes.
Why loaddata is slow
For every fixture file, loaddata:
- finds and parses the file (Django uses libyaml's C loader for YAML when it's installed, otherwise the much slower pure-Python one)
- deserializes each object into a model instance, resolving natural keys with extra queries if you use them
- saves each object individually. Fixture objects have a primary key, so Django first tries an
UPDATEand falls back to anINSERTwhen no row matched: two queries per object, pluspre_saveandpost_savesignals - resets the database sequences and checks constraints
A fixture with 2,000 objects means about 4,000 queries. That's fine once. Before each of 300 tests, it's a large share of the suite.
When fixtures load per test
How often fixtures = [...] loads depends on the test class:
| Where | How often the fixture loads |
|---|---|
TestCase with fixtures = [...] |
once per class, inside the class transaction |
TransactionTestCase (and LiveServerTestCase) with fixtures = [...] |
before every test, after the flush |
a function-scoped pytest fixture calling call_command("loaddata", ...) |
before every test |
the same big fixture listed on many TestCase classes |
once per class, so many times per run |
Load them once per class: TestCase
If the tests are in a TransactionTestCase, switching to TestCase alone turns per-test loading into per-class loading, and each test still sees the fixture data unchanged because of the rollback:
from django.test import TestCase
class InvoiceTests(TestCase):
fixtures = ["plans.json", "currencies.json"]
def test_default_currency(self): ...
See TestCase instead of TransactionTestCase for which tests can switch.
Load them once per session: pytest-django
For reference data almost every test needs, load it once for the whole run. pytest-django documents extending its django_db_setup fixture for this:
# conftest.py
import pytest
from django.core.management import call_command
@pytest.fixture(scope="session")
def django_db_setup(django_db_setup, django_db_blocker):
with django_db_blocker.unblock():
call_command("loaddata", "plans.json", "currencies.json")
Requesting django_db_setup inside the override runs pytest-django's original first, so the test database exists when loaddata runs. The data is committed, so it's there for every test that uses @pytest.mark.django_db or the db fixture, and each test's changes to it are still rolled back. With --reuse-db the rows survive between runs too, and loading them again just updates them in place.
The catch: transactional tests (transaction=True, transactional_db, live_server) flush every table afterwards, including this data. pytest-django runs them after the other tests, but any test that runs after a flush won't find the data. Those tests need to load what they use themselves.
Replace fixtures with factories
Fixture files also tend to grow: a test needs one more plan, someone runs dumpdata, and now every test loads 5,000 objects to use three. Creating exactly what a test class uses is usually faster and easier to read:
from django.test import TestCase
from model_bakery import baker
from billing.models import Currency, Plan
class InvoiceTests(TestCase):
@classmethod
def setUpTestData(cls):
cls.eur = baker.make(Currency, code="EUR")
cls.plan = baker.make(Plan, currency=cls.eur, monthly_price=10)
setUpTestData runs once per class, like class-level fixtures, and you can see in the test what data it depends on. See setUpTestData instead of setUp and faster test factories.
If you keep fixture files:
- split them so each class loads only what it needs
- prefer JSON over YAML, or install libyaml so PyYAML has its C loader
- for data production needs too (plans, countries), a data migration creates it once when the test database is built, but remember that
TransactionTestCaseflushes delete it
Keep the tests correct
Data loaded once per class is rolled back per test only inside TestCase's transaction. Session-loaded data is committed: tests that modify it are fine under @pytest.mark.django_db, because their changes roll back, but transactional tests delete it. When you replace a fixture with factories, check that the tests still assert on the same values; a test that silently stops finding its data can pass for the wrong reason.
How flakytest finds it
flakytest puts time spent in loaddata and Django's serializers in a fixture loading category. When it's at least a second and 2% of test time, it suggests Load test data once per class, not once per test, with the tests that spent the most time loading and the lines that triggered it.
Further reading
- Django: fixture loading in tests
- Django:
loaddata - pytest-django: populate the database with initial test data
Related guides
- Create shared test data in setUpTestData instead of setUp: Django runs setUp before every test, so its rows are inserted again each time. setUpTestData creates them once per TestCase class, and every test still starts clean.
- Use TestCase instead of TransactionTestCase: TransactionTestCase and transaction=True empty every table after each test. Django's TestCase rolls back a transaction instead. When to use which, and how to switch.
- Faster factory_boy and model_bakery factories: Slow factory_boy and model_bakery factories? Create shared data once, use build() or baker.prepare() instead of saving, bulk create rows, reuse one Faker instance and tune Hypothesis.
- Stop re-running migrations on every test run: Django runs every migration before the first test, on every run. Keep the test database with --reuse-db or --keepdb, or skip migrations with --nomigrations.
Updated October 18, 2026 · Suggest a change