# 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:

```python
from django.test import TestCase


class InvoiceTests(TestCase):
    fixtures = ["plans.json", "currencies.json"]

    def test_default_currency(self): ...
```

See [TestCase instead of TransactionTestCase](https://flakytest.dev/guides/testcase-vs-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:

```python
# 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:

```python
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](https://flakytest.dev/guides/setuptestdata-vs-setup/) and [faster test factories](https://flakytest.dev/guides/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

- [Django: fixture loading in tests](https://docs.djangoproject.com/en/stable/topics/testing/tools/#fixture-loading)
- [Django: `loaddata`](https://docs.djangoproject.com/en/stable/ref/django-admin/#loaddata)
- [pytest-django: populate the database with initial test data](https://pytest-django.readthedocs.io/en/latest/database.html#populate-the-database-with-initial-test-data)
