Speed up your test suite

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:

  1. finds and parses the file (Django uses libyaml's C loader for YAML when it's installed, otherwise the much slower pure-Python one)
  2. deserializes each object into a model instance, resolving natural keys with extra queries if you use them
  3. saves each object individually. Fixture objects have a primary key, so Django first tries an UPDATE and falls back to an INSERT when no row matched: two queries per object, plus pre_save and post_save signals
  4. 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 TransactionTestCase flushes 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

Related guides

Updated October 18, 2026 · Suggest a change