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-boyFor Django projects, factory_boy also uses Faker under the hood for generating realistic fake data:
pip install factory-boy fakerBasic 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}'
)Related Objects (SubFactory)
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 recordsbuild
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 modelUsing 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 == userOr 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 NoneUsing 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_oneFix: 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.