factory_boy: Python Test Factories for Django, SQLAlchemy, and More

factory_boy: Python Test Factories for Django, SQLAlchemy, and More

factory_boy is the standard Python library for test factories. It integrates directly with Django's ORM, SQLAlchemy, MongoEngine, and plain Python classes — and it removes the boilerplate of constructing test objects one field at a time.

This guide covers everything from basic factories to advanced patterns used in production test suites.

Installation

pip install factory-boy

For Django projects, factory_boy also uses Faker under the hood for generating realistic fake data:

pip install factory-boy faker

Basic Factory

import factory
from myapp.models import User

class UserFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = User
    
    email = factory.Faker('email')
    first_name = factory.Faker('first_name')
    last_name = factory.Faker('last_name')
    is_active = True
    role = 'user'

Using it in tests:

from myapp.tests.factories import UserFactory

# Create and save to database
user = UserFactory()

# Override specific fields
admin = UserFactory(role='admin')

# Create without saving (build strategy)
user_obj = UserFactory.build()

# Create multiple
users = UserFactory.create_batch(5)
admins = UserFactory.create_batch(3, role='admin')

Faker Integration

factory_boy wraps Python's Faker library through factory.Faker:

import factory

class ProfileFactory(factory.Factory):
    class Meta:
        model = dict
    
    name = factory.Faker('name')
    email = factory.Faker('email')
    phone = factory.Faker('phone_number')
    address = factory.Faker('address')
    bio = factory.Faker('paragraph')
    website = factory.Faker('url')
    company = factory.Faker('company')
    job_title = factory.Faker('job')
    birthdate = factory.Faker('date_of_birth', minimum_age=18, maximum_age=65)

Locale-Specific Faker

class GermanUserFactory(factory.Factory):
    class Meta:
        model = dict
    
    name = factory.Faker('name', locale='de_DE')
    city = factory.Faker('city', locale='de_DE')
    phone = factory.Faker('phone_number', locale='de_DE')

Sequences

When you need unique incrementing values:

import factory

class UserFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = User
    
    # Generates: user1@example.com, user2@example.com, ...
    email = factory.Sequence(lambda n: f'user{n}@example.com')
    username = factory.Sequence(lambda n: f'user{n}')

Sequences reset between test runs and are per-factory.

Lazy Attributes

LazyAttribute lets you derive one field from others:

class UserFactory(factory.Factory):
    class Meta:
        model = dict
    
    first_name = factory.Faker('first_name')
    last_name = factory.Faker('last_name')
    
    # email is derived from first_name and last_name
    email = factory.LazyAttribute(
        lambda obj: f'{obj.first_name.lower()}.{obj.last_name.lower()}@example.com'
    )
    
    # full_name depends on first and last
    full_name = factory.LazyAttribute(
        lambda obj: f'{obj.first_name} {obj.last_name}'
    )

Use SubFactory to create related objects automatically:

from myapp.models import User, Post, Comment

class UserFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = User
    email = factory.Faker('email')

class PostFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Post
    
    title = factory.Faker('sentence', nb_words=6)
    content = factory.Faker('paragraphs', nb=3, variable_nb_paragraphs=True)
    author = factory.SubFactory(UserFactory)  # Creates a User automatically
    published_at = factory.Faker('date_time_this_year')

class CommentFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Comment
    
    body = factory.Faker('paragraph')
    post = factory.SubFactory(PostFactory)   # Creates Post + User automatically
    author = factory.SubFactory(UserFactory)

Using them:

# Creates Comment + Post + User automatically
comment = CommentFactory()

# Reuse an existing post
post = PostFactory()
comments = CommentFactory.create_batch(5, post=post)

Traits

Traits define named configurations for common states:

class UserFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = User
    
    email = factory.Faker('email')
    is_active = True
    role = 'user'
    email_verified = True
    
    class Params:
        # Admin trait
        admin = factory.Trait(
            role='admin',
            is_staff=True,
        )
        
        # Inactive trait
        inactive = factory.Trait(
            is_active=False,
            deactivated_at=factory.Faker('date_time_this_year'),
        )
        
        # Unverified trait
        unverified = factory.Trait(
            email_verified=False,
            verification_token=factory.Faker('uuid4'),
        )

Usage:

admin = UserFactory(admin=True)
inactive_user = UserFactory(inactive=True)
unverified_admin = UserFactory(admin=True, unverified=True)

Many-to-Many Relationships

Use post_generation for M2M fields:

from myapp.models import Article, Tag

class TagFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Tag
    name = factory.Faker('word')

class ArticleFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Article
    
    title = factory.Faker('sentence')
    body = factory.Faker('paragraphs', nb=5)
    
    @factory.post_generation
    def tags(self, create, extracted, **kwargs):
        if not create:
            return
        
        if extracted:
            # Tags passed explicitly
            for tag in extracted:
                self.tags.add(tag)
        else:
            # Create 2 tags by default
            self.tags.add(TagFactory(), TagFactory())

Usage:

# Creates article with 2 auto-generated tags
article = ArticleFactory()

# Creates article with specific tags
tag1 = TagFactory(name='python')
tag2 = TagFactory(name='testing')
article = ArticleFactory(tags=[tag1, tag2])

Strategies

factory_boy supports three creation strategies:

create (default)

Saves to database. Use for integration tests.

user = UserFactory()                  # saved
user = UserFactory.create()           # same
users = UserFactory.create_batch(5)   # 5 saved records

build

Constructs the object but doesn't save it. Use for unit tests that don't need the database.

user = UserFactory.build()
users = UserFactory.build_batch(3)

stub

Creates a stub object with the same attributes but no model class. Useful when testing code that only uses attribute access:

user_stub = UserFactory.stub()
print(user_stub.email)   # works, no DB, no model

Using with pytest

Install pytest-factoryboy to auto-inject factories as pytest fixtures:

pip install pytest-factoryboy
# conftest.py
from pytest_factoryboy import register
from myapp.tests.factories import UserFactory, PostFactory

register(UserFactory)
register(PostFactory)
# test_posts.py — factories auto-injected
def test_post_author(user, post_factory):
    post = post_factory(author=user)
    assert post.author == user

Or use factories directly in fixtures:

# conftest.py
import pytest
from myapp.tests.factories import UserFactory, PostFactory

@pytest.fixture
def admin_user(db):
    return UserFactory(admin=True)

@pytest.fixture
def published_post(db, admin_user):
    return PostFactory(author=admin_user, status='published')

Django-Specific Patterns

Database Access in Tests

factory_boy's DjangoModelFactory requires database access. Mark tests with @pytest.mark.django_db:

@pytest.mark.django_db
def test_user_full_name(user_factory):
    user = user_factory(first_name='Jane', last_name='Doe')
    assert user.get_full_name() == 'Jane Doe'

Using create_or_update

When your model has a unique constraint and you don't want duplicates:

class CategoryFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Category
        django_get_or_create = ('slug',)  # Use existing if slug matches
    
    name = factory.Faker('word')
    slug = factory.LazyAttribute(lambda obj: obj.name.lower())

File Fields

For Django FileField and ImageField:

from django.core.files.base import ContentFile

class AvatarFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = UserProfile
    
    avatar = factory.django.ImageField(
        filename='avatar.jpg',
        width=200,
        height=200,
    )

SQLAlchemy Factories

import factory
from factory.alchemy import SQLAlchemyModelFactory
from myapp.database import Session
from myapp.models import User

class UserFactory(SQLAlchemyModelFactory):
    class Meta:
        model = User
        sqlalchemy_session = Session
        sqlalchemy_session_persistence = 'commit'  # or 'flush'
    
    email = factory.Faker('email')
    name = factory.Faker('name')

Plain Python (Non-ORM) Factories

For dataclasses, Pydantic models, or any Python class:

from dataclasses import dataclass
import factory

@dataclass
class Address:
    street: str
    city: str
    country: str
    postal_code: str

class AddressFactory(factory.Factory):
    class Meta:
        model = Address
    
    street = factory.Faker('street_address')
    city = factory.Faker('city')
    country = factory.Faker('country')
    postal_code = factory.Faker('postcode')

For Pydantic:

from pydantic import BaseModel
import factory

class Product(BaseModel):
    id: str
    name: str
    price: float

class ProductFactory(factory.Factory):
    class Meta:
        model = Product
    
    id = factory.Faker('uuid4')
    name = factory.Faker('catch_phrase')
    price = factory.Faker('pyfloat', min_value=1, max_value=1000, right_digits=2)

Organizing Factories

For a mid-to-large Django project:

myapp/
  tests/
    factories/
      __init__.py          # exports all factories
      user_factories.py
      order_factories.py
      product_factories.py
    conftest.py
    test_views.py
    test_models.py
# tests/factories/__init__.py
from .user_factories import UserFactory, AdminFactory
from .order_factories import OrderFactory, OrderItemFactory
from .product_factories import ProductFactory, CategoryFactory

__all__ = [
    'UserFactory', 'AdminFactory',
    'OrderFactory', 'OrderItemFactory',
    'ProductFactory', 'CategoryFactory',
]

Common Mistakes

Forgetting @pytest.mark.django_db

# Fails — no DB access
def test_create_user():
    user = UserFactory()  # TransactionManagementError

# Correct
@pytest.mark.django_db
def test_create_user():
    user = UserFactory()
    assert user.id is not None

Using create When build Is Enough

Unit tests that don't touch the database are faster. Use build when the code under test doesn't need persisted objects:

# Slow — hits DB unnecessarily for a pure function test
def test_full_name():
    user = UserFactory(first_name='Jane', last_name='Doe')
    assert get_display_name(user) == 'Jane D.'

# Fast — no DB
def test_full_name():
    user = UserFactory.build(first_name='Jane', last_name='Doe')
    assert get_display_name(user) == 'Jane D.'

Shared Mutable Factories at Module Scope

# Dangerous — shared instance gets mutated by tests
user = UserFactory()  # module-level

def test_one(self):
    user.role = 'admin'  # mutates shared instance

def test_two(self):
    assert user.role == 'user'  # fails — role was changed by test_one

Fix: create inside each test or use pytest fixtures.

Summary

factory_boy is the backbone of a healthy Python test suite:

  • Factories replace fragile fixtures with composable, schema-resilient object construction
  • Traits make common states named and reusable
  • SubFactory handles related objects automatically
  • Sequences guarantee unique values without hardcoding
  • Strategies (create, build, stub) give you the right persistence level for each test type

Install it, write one factory per model, and your test setup code shrinks from 30 lines to 3.

Read more