factory_boy for Django: Test Data Generation Guide
Hardcoded fixture files rot. setUpTestData with manual objects.create() calls sprawl. factory_boy solves test data generation in Django by centralizing model construction, providing sensible defaults, and making it easy to override only the fields that matter for a specific test.
Installation
pip install factory-boyYour First DjangoModelFactory
# tests/factories.py
import factory
from django.contrib.auth import get_user_model
from myapp.models import Category, Product, Order
User = get_user_model()
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = User
username = factory.Sequence(lambda n: f"user{n}")
email = factory.LazyAttribute(lambda obj: f"{obj.username}@example.com")
password = factory.PostGenerationMethodCall("set_password", "defaultpass")
is_active = TrueDjangoModelFactory calls Model.objects.create() by default. Sequence generates unique values per factory call. LazyAttribute computes values from other fields on the same object.
Use it in tests:
from django.test import TestCase
from tests.factories import UserFactory
class UserTests(TestCase):
def test_user_created(self):
user = UserFactory()
self.assertIsNotNone(user.pk)
self.assertTrue(user.username.startswith("user"))
def test_override_fields(self):
user = UserFactory(username="alice", email="alice@example.com")
self.assertEqual(user.username, "alice")
self.assertEqual(user.email, "alice@example.com")Sequence
Sequence increments a counter on every factory call. The counter is per-factory and resets between test classes (when using factory.reset_sequence()).
class CategoryFactory(factory.django.DjangoModelFactory):
class Meta:
model = Category
name = factory.Sequence(lambda n: f"Category {n}")
slug = factory.Sequence(lambda n: f"category-{n}")Each call to CategoryFactory() produces a unique name: Category 0, Category 1, Category 2, etc.
LazyAttribute
LazyAttribute computes a field's value at factory instantiation time, with access to the partially-constructed object.
class ProductFactory(factory.django.DjangoModelFactory):
class Meta:
model = Product
name = factory.Sequence(lambda n: f"Product {n}")
slug = factory.LazyAttribute(lambda obj: obj.name.lower().replace(" ", "-"))
price = factory.Faker("pydecimal", left_digits=3, right_digits=2, positive=True)
stock = factory.Faker("random_int", min=0, max=1000)
description = factory.Faker("paragraph")
is_active = Truefactory.Faker delegates to the Faker library. This gives you realistic-looking but random data — paragraph text, prices, email addresses — without managing uniqueness manually.
SubFactory
Use SubFactory to express relationships between models. The related object is created automatically unless you supply your own.
class ProductFactory(factory.django.DjangoModelFactory):
class Meta:
model = Product
name = factory.Sequence(lambda n: f"Product {n}")
price = 9.99
category = factory.SubFactory(CategoryFactory)# Automatically creates a Category and a Product linked to it
product = ProductFactory()
# Reuse an existing category
category = CategoryFactory(name="Electronics")
product = ProductFactory(category=category)SubFactory creates the related object using CategoryFactory(). If you provide a value directly, SubFactory is bypassed.
RelatedFactory and post_generation
For reverse relationships (ManyToMany, reverse FK), use RelatedFactory or @factory.post_generation.
class OrderFactory(factory.django.DjangoModelFactory):
class Meta:
model = Order
user = factory.SubFactory(UserFactory)
status = "pending"
total = factory.Faker("pydecimal", left_digits=4, right_digits=2, positive=True)
@factory.post_generation
def items(self, create, extracted, **kwargs):
if not create:
return
if extracted:
for item in extracted:
self.items.add(item)Usage:
# Order with no items
order = OrderFactory()
# Order with specific items
item1 = OrderItemFactory()
item2 = OrderItemFactory()
order = OrderFactory(items=[item1, item2])create, build, and create_batch
factory_boy provides three creation strategies:
# create() — saves to database (default)
product = ProductFactory()
assert product.pk is not None
# build() — instantiates in memory, no database call
product = ProductFactory.build()
assert product.pk is None
# create_batch() — saves N instances
products = ProductFactory.create_batch(10)
assert len(products) == 10
# build_batch() — N instances in memory
products = ProductFactory.build_batch(5)Use build() when testing model methods that don't need persistence — it's faster and avoids database overhead.
Traits
Traits are named groups of field overrides. They keep tests readable by naming intent, not implementation.
class ProductFactory(factory.django.DjangoModelFactory):
class Meta:
model = Product
exclude = ["is_discounted"] # Trait declaration field
name = factory.Sequence(lambda n: f"Product {n}")
price = 100.00
discount_price = None
is_active = True
class Params:
is_discounted = factory.Trait(
discount_price=factory.LazyAttribute(lambda obj: obj.price * 0.8),
)
out_of_stock = factory.Trait(
stock=0,
is_active=False,
)
premium = factory.Trait(
price=factory.Faker("pydecimal", left_digits=4, right_digits=2, positive=True),
)# In tests
discounted_product = ProductFactory(is_discounted=True)
oos_product = ProductFactory(out_of_stock=True)
premium_product = ProductFactory(premium=True, is_discounted=True)Traits compose — you can activate multiple at once.
Handling Unique Constraints
When unique=True fields need predictable values, Sequence is your friend:
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = User
username = factory.Sequence(lambda n: f"user_{n}")
email = factory.Sequence(lambda n: f"user_{n}@example.com")For unique_together constraints:
class TagFactory(factory.django.DjangoModelFactory):
class Meta:
model = Tag
django_get_or_create = ("name",) # Use get_or_create semantics
name = factory.Sequence(lambda n: f"Tag {n}")django_get_or_create tells factory_boy to call get_or_create instead of create. This prevents IntegrityError when two factories try to create the same tag.
Factory Inheritance
Avoid duplicating field definitions with factory inheritance:
class BaseProductFactory(factory.django.DjangoModelFactory):
class Meta:
model = Product
abstract = True # Won't be used directly
name = factory.Sequence(lambda n: f"Product {n}")
price = 9.99
category = factory.SubFactory(CategoryFactory)
class DigitalProductFactory(BaseProductFactory):
is_digital = True
download_url = factory.Faker("url")
class PhysicalProductFactory(BaseProductFactory):
weight_kg = factory.Faker("pydecimal", left_digits=1, right_digits=2, positive=True)
requires_shipping = TrueIntegrating with TestCase
from django.test import TestCase
from tests.factories import ProductFactory, CategoryFactory, UserFactory
class CartTests(TestCase):
def setUp(self):
self.user = UserFactory()
self.category = CategoryFactory(name="Electronics")
self.products = ProductFactory.create_batch(3, category=self.category, stock=10)
def test_add_product_to_cart(self):
product = self.products[0]
cart = Cart.objects.create(user=self.user)
cart.add(product, quantity=2)
self.assertEqual(cart.total, product.price * 2)
def test_out_of_stock_product_cannot_be_added(self):
oos = ProductFactory(out_of_stock=True)
cart = Cart.objects.create(user=self.user)
with self.assertRaises(ValueError, msg="Product out of stock"):
cart.add(oos, quantity=1)Recommended Project Layout
tests/
__init__.py
factories.py # All factories in one file for small projects
# OR
factories/
__init__.py # Re-exports all factories
users.py
products.py
orders.py
test_models.py
test_views.pyFor large projects, split factories by domain and import from a single factories/__init__.py:
# tests/factories/__init__.py
from .users import UserFactory
from .products import ProductFactory, CategoryFactory
from .orders import OrderFactory, OrderItemFactory
__all__ = [
"UserFactory",
"ProductFactory",
"CategoryFactory",
"OrderFactory",
"OrderItemFactory",
]factory_boy vs fixtures vs setUpTestData
| Approach | Speed | Maintainability | Flexibility |
|---|---|---|---|
loaddata fixtures |
Slow | Hard (JSON/YAML) | Low |
setUpTestData |
Fast | Medium | Medium |
| factory_boy | Fast | High | High |
factory_boy wins on flexibility and maintainability. The initial setup cost is a one-time investment; every test after that is cleaner.
Combine factory_boy with tools like HelpMeTest to verify that the data your factories create matches what real users actually see in the deployed application.