factory_boy in Django: A Better Alternative to Fixtures

Blog / Django · August 7, 2021 · Updated June 10, 2026 · 8 min read
factory_boy in Django: A Better Alternative to Fixtures

If you have written Django tests for any length of time, you have probably felt the pain of static fixtures. A fixtures/initial_data.json (or YAML) file looks tidy at first, but it rots: add a non-null column and every fixture breaks, hard-coded primary keys collide, and a single test ends up depending on dozens of unrelated rows it never reads. factory_boy fixes this by replacing static data files with small, declarative factories that build exactly the objects a test needs — and only the fields that test cares about.

This guide covers the modern factory_boy API (Python 3, Django 5.x): DjangoModelFactory, the integrated Faker provider, Sequence, LazyAttribute, SubFactory, RelatedFactory, post_generation, Traits, batches, and how to wire it all into both pytest-django and Django's TestCase. If you are still scaffolding your project, our creating a Django app walkthrough is a good companion.

Why static fixtures break down

Django's built-in fixtures (loaddata, TestCase.fixtures, --fixtures) serialize whole rows to JSON/YAML. That model has three structural problems:

  • They go stale on every schema change. Add a required field and all of your fixtures must be hand-edited, or loaddata fails.
  • Relations are encoded as raw primary keys. A fixture says "author": 7, so you have to keep PKs in sync across files by hand — brittle and unreadable.
  • Tests become coupled to data they do not use. A fixture file loads the whole graph, so a test for one model silently depends on twenty other rows.

Factories invert this. Instead of "here is a frozen snapshot of the database", you say "build me a valid Article" and override only the one field under test. The factory fills in the rest with sensible, realistic defaults.

Installing factory_boy

factory_boy ships its own Django integration and bundles Faker for realistic data.

pip install factory_boy

# If you test with pytest (recommended), add the Django plugin and the
# factory_boy <-> pytest bridge:
pip install pytest-django pytest-factoryboy

Your first factory

Put factories next to your tests, e.g. myapp/tests/factories.py. A factory is just a class that maps model fields to declarations. Here is a factory for the user model:

# myapp/tests/factories.py
import factory
from django.contrib.auth import get_user_model

User = get_user_model()


class UserFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = User
        django_get_or_create = ("username",)

    username = factory.Sequence(lambda n: f"user{n}")
    first_name = factory.Faker("first_name")
    last_name = factory.Faker("last_name")
    email = factory.LazyAttribute(lambda obj: f"{obj.username}@example.com")
    is_active = True
    # Run a method on the instance after build/create (hashes the password):
    password = factory.PostGenerationMethodCall("set_password", "test-pass-123")

A few declarations are doing the heavy lifting here:

  • factory.Faker("first_name") — pulls a realistic value from the bundled faker library. There is a provider for almost everything ("email", "sentence", "date_time", "company", "address"…). This integrated form is preferred over the older factory.fuzzy helpers, which are now largely superseded.
  • factory.Sequence(lambda n: ...) — guarantees uniqueness across instances (user0, user1, …), perfect for unique columns.
  • factory.LazyAttribute(lambda obj: ...) — computes a value from the other fields of the same instance. Use factory.LazyFunction(timezone.now) when you just need to call a function that ignores the instance.
  • django_get_or_create = ("username",) — tells factory_boy to call User.objects.get_or_create(username=...) instead of always inserting, which avoids duplicate-key errors when a fixture is requested twice.

Create one with UserFactory(), and override any field per test: UserFactory(username="ada", is_active=False).

Relations: SubFactory, RelatedFactory and post_generation

Real models have foreign keys and many-to-many relations. factory_boy expresses each relationship type with its own declaration. Say we have these models:

# myapp/models.py
from django.conf import settings
from django.db import models


class Tag(models.Model):
    name = models.CharField(max_length=50, unique=True)


class Article(models.Model):
    DRAFT, PUBLISHED = "draft", "published"
    STATUS_CHOICES = [(DRAFT, "Draft"), (PUBLISHED, "Published")]

    author = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="articles"
    )
    title = models.CharField(max_length=200)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=DRAFT)
    created_at = models.DateTimeField(auto_now_add=True)
    tags = models.ManyToManyField(Tag, blank=True)
# myapp/tests/factories.py (continued)
from myapp.models import Article, Tag


class TagFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Tag
        django_get_or_create = ("name",)

    name = factory.Iterator(["python", "django", "testing", "api"])


class ArticleFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Article

    author = factory.SubFactory(UserFactory)   # FK: builds a User automatically
    title = factory.Faker("sentence", nb_words=6)
    status = Article.DRAFT

    class Params:
        # A Trait flips several fields together by passing published=True
        published = factory.Trait(status=Article.PUBLISHED)

    @factory.post_generation
    def tags(self, create, extracted, **kwargs):
        if not create:
            return                     # build(): nothing is saved, skip the M2M
        if extracted:
            self.tags.set(extracted)   # ArticleFactory(tags=[t1, t2])
        else:
            self.tags.add(TagFactory())

The relationship declarations map cleanly onto Django's relation types:

  • factory.SubFactory(UserFactory) — for a forward FK. When you create an Article, factory_boy creates its author for you. Override it (ArticleFactory(author=some_user)) and the SubFactory is skipped.
  • @factory.post_generation — a hook that runs after the object exists, ideal for many-to-many relations (you cannot set M2M before the row has a PK) or any extra setup. The create flag lets you no-op for build().
  • factory.RelatedFactory(...) — for a reverse FK (one-to-many): use it on the User side to auto-create related Article rows when a user is made.
  • factory.Iterator([...]) — cycles through a fixed set of values across instances, handy for status enums or categories.
  • factory.Trait(...) inside class Params — bundles related overrides behind one readable flag (ArticleFactory(published=True)).

build() vs create() vs batches

The single biggest performance lever in factory_boy is choosing between build (in-memory, no DB) and create (persisted). Reach for build() in pure-logic and serializer tests to skip the database entirely.

# Unsaved instance — no DB hit, fast. SubFactories are also only built.
article = ArticleFactory.build()

# Saved instance (the default). Resolves SubFactory and post_generation.
article = ArticleFactory.create()
article = ArticleFactory()            # identical to .create()

# Declare ONLY what the test cares about; everything else is auto-filled.
launch = ArticleFactory(status=Article.PUBLISHED, title="Launch day")
launch = ArticleFactory(published=True)        # same thing, via the Trait

# Batches for list / pagination / queryset tests
ArticleFactory.create_batch(5, author=some_user)
drafts = ArticleFactory.build_batch(3)

Using factories in Django's TestCase

Factories drop straight into Django's standard TestCase. There is no fixtures attribute and no JSON file to maintain — each test states the exact data it needs:

# myapp/tests/test_articles.py
from django.test import TestCase

from myapp.models import Article
from myapp.tests.factories import ArticleFactory, UserFactory


class ArticleQuerysetTests(TestCase):
    def test_author_published_articles(self):
        author = UserFactory()
        ArticleFactory.create_batch(3, author=author, published=True)
        ArticleFactory(author=author, status=Article.DRAFT)  # noise

        published = author.articles.filter(status=Article.PUBLISHED)
        self.assertEqual(published.count(), 3)

Using factory_boy with pytest-django

With pytest-django you have two clean options. The first is to wrap a factory in a normal pytest.fixture. The second — and the one I reach for on larger suites — is the pytest-factoryboy plugin: register(UserFactory) exposes both a user_factory fixture (the factory itself) and a user fixture (a ready instance), with automatic dependency wiring between registered factories.

# conftest.py
import pytest
from pytest_factoryboy import register

from myapp.tests.factories import UserFactory, ArticleFactory

register(UserFactory)     # -> "user" and "user_factory" fixtures
register(ArticleFactory)  # -> "article" and "article_factory" fixtures


# You can also hand-roll a fixture that wraps a factory:
@pytest.fixture
def author(db):
    return UserFactory()
# myapp/tests/test_articles.py
import pytest


@pytest.mark.django_db
def test_author_article_count(article_factory, user):
    # article_factory + user come from pytest-factoryboy register()
    article_factory.create_batch(2, author=user)
    assert user.articles.count() == 2


@pytest.mark.django_db
def test_article_has_author(article):
    # "article" is a ready-made instance injected by the plugin
    assert article.author is not None

factory_boy vs Django fixtures vs model_bakery

A worthwhile alternative is model_bakery (formerly model_mommy). It auto-fills every required field with no declarations at all (baker.make(Article)), which is great for "I just need a valid row" tests. The trade-off is control: factory_boy's explicit declarations make intent obvious and realistic data easy, while model_bakery optimises for zero boilerplate.

Concern Django fixtures (JSON/YAML) factory_boy model_bakery
Schema change Break, edit every file by hand Update one factory Auto-fills new fields
Field values Static, hard-coded Declarative + Faker (realistic) Auto-generated defaults
Relations (FK/M2M) Manual PKs, fragile SubFactory / post_generation Auto-creates related rows
Test clarity Hidden in data files Only the fields a test cares about Minimal, but implicit
Speed Loads whole file per test build() skips the DB Recipes for reuse
Control Low High (explicit) Low (magic defaults)

For most teams, factory_boy hits the sweet spot: realistic, composable data with enough control to keep tests readable. Pair it with end-to-end checks — see our guide on unit testing with Selenium in Python — for full coverage.

Frequently Asked Questions

What is the difference between factory_boy and Django fixtures?

Django fixtures are static JSON/YAML snapshots loaded with loaddata; factory_boy is a declarative factory library that builds model instances on demand. Fixtures break on every schema change and encode relations as raw primary keys, while factories auto-fill defaults, generate realistic data with Faker, resolve foreign keys via SubFactory, and let each test override only the fields it cares about. For test data, factory_boy is almost always the more maintainable choice.

What is the difference between build() and create() in factory_boy?

build() returns an in-memory instance and never touches the database, so it is fast and ideal for pure-logic, form, or serializer tests. create() (the default, also called via Factory()) saves the instance, resolves any SubFactory relations, and runs post_generation hooks. Use build() when you do not need persistence, and create() when the test reads from the database. Batch variants build_batch(n) and create_batch(n) produce lists.

How do I handle foreign keys and many-to-many relations?

Use factory.SubFactory(OtherFactory) for a forward foreign key — it creates the related object automatically and is skipped if you pass your own. Use factory.RelatedFactory for a reverse one-to-many. For many-to-many relations, use a @factory.post_generation hook, because M2M links can only be set after the row has a primary key; guard it with the create flag so build() stays DB-free.

How do I use factory_boy with pytest?

Either wrap a factory in a @pytest.fixture that returns MyFactory(), or install pytest-factoryboy and call register(MyFactory) in conftest.py. Registration gives you a my_factory fixture (the factory) and a my_model fixture (a ready instance), and wires registered factories together automatically. Remember to mark database tests with @pytest.mark.django_db (provided by pytest-django).

Should I use factory_boy or model_bakery?

Choose factory_boy when you want explicit, readable, realistic data and fine control over relations and traits — it scales well to large suites. Choose model_bakery (formerly model_mommy) when you just need a valid row with zero setup: baker.make(Model) auto-fills every required field. Many teams standardise on factory_boy for its clarity and Faker integration, but model_bakery is a fine lightweight alternative.

How do I generate realistic fake data?

Use the integrated factory.Faker(...) declaration, which pulls from the bundled faker library: factory.Faker("name"), factory.Faker("email"), factory.Faker("sentence", nb_words=6), factory.Faker("date_time"), and hundreds more providers. For values derived from other fields use factory.LazyAttribute, and for unique sequential values use factory.Sequence. The older factory.fuzzy module still works but is largely superseded by Faker.

Building reliable Django test suites

Swapping brittle JSON fixtures for factory_boy is one of the highest-leverage changes you can make to a Django test suite: tests get shorter, survive schema changes, and clearly state their own preconditions. Start by replacing your most-edited fixture with a factory, add Faker for realistic values, and lean on build() to keep the suite fast.

At MicroPyramid we have shipped 50+ Python and Django projects over 12+ years, and a well-factored test suite is part of how we deliver features in days, not months. If you want help here, explore our Django development services and software testing & QA services.

Share this article