Test Data: Fixtures vs Factories vs Seeding
Every test needs data to work on. There are four ways to get it, and picking the right one — and defending your choice — is a real skill.
The idea, in one line
Get your test's data from the lowest layer that's still reliable, and check your result at the layer you're actually testing. Everything below is how to do that well.
The four strategies
- Fixtures (pytest) — set up and tear down state around a test: a logged-in session, a temp file, a DB connection. Their scope (function, class, module, session) controls how often the setup runs. Use them to wire things together, not to hold big blobs of data.
- Factories — build objects on demand with realistic defaults and easy overrides (factory_boy, or a hand-rolled builder). Each test mints its own fresh user, so tests stay independent instead of sharing one fragile object.
- API seeding — create preconditions by calling the app's own endpoints, like
POST /usersbefore a UI test. Realistic, and stays valid as the schema changes, but slower. - DB seeding — insert rows straight into the database. Fastest, and can reach states the UI can't, but it skips validation and ties your tests to the schema.
import pytest
# Factory: fresh, independent data per test
def make_user(**overrides):
base = {"email": "t@example.com", "plan": "free", "verified": True}
return {**base, **overrides}
# Fixture: API-seed a real user, then clean up after
@pytest.fixture
def seeded_user(api_client):
payload = make_user(plan="pro")
resp = api_client.post("/users", json=payload) # seed via the app's API
user = resp.json()
yield user # test runs here
api_client.delete(f"/users/{user['id']}") # teardown
def test_pro_dashboard(seeded_user, ui):
ui.login(seeded_user)
assert ui.dashboard.badge() == "PRO"Read it top to bottom: the factory makes clean data, the fixture uses that data to seed a real user through the API, the test runs at the yield, and everything after yield cleans up. Whatever you create, you remove.
Advanced — the heuristic to say out loud
Seed setup at the lowest reliable layer, then assert at the layer under test. Prefer API seeding over DB seeding unless speed forces your hand — DB seeding is fast but bypasses the app's own rules, so it can create states that could never happen for a real user.
Grounded in the pytest fixtures docs and the factory_boy documentation