factory_boy for Django: Test Data Generation Guide

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-boy

Your 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 = True

DjangoModelFactory 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 = True

factory.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 = True

Integrating 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)
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.py

For 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.

Read more

Start now free