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
loaddatafails. - 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-factoryboyYour 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 bundledfakerlibrary. There is a provider for almost everything ("email","sentence","date_time","company","address"…). This integrated form is preferred over the olderfactory.fuzzyhelpers, 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. Usefactory.LazyFunction(timezone.now)when you just need to call a function that ignores the instance.django_get_or_create = ("username",)— tells factory_boy to callUser.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 anArticle, factory_boy creates itsauthorfor 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. Thecreateflag lets you no-op forbuild().factory.RelatedFactory(...)— for a reverse FK (one-to-many): use it on theUserside to auto-create relatedArticlerows when a user is made.factory.Iterator([...])— cycles through a fixed set of values across instances, handy for status enums or categories.factory.Trait(...)insideclass 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 Nonefactory_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.