Intermediate
You know the feeling: you sit down to write a test for a new feature, and before writing a single assertion you spend 20 minutes constructing fake User objects, filling in every required field, inventing email addresses, and wiring up foreign keys just to get your test setup in a valid state. By the time the setup is done, you have forgotten what you were actually testing. This is the test fixture problem, and it quietly kills both your productivity and your test suite’s readability.
The factory_boy library solves this with factory classes that act as blueprints for your objects. Install it once with pip install factory-boy, define a factory that knows how to build a valid User, and from that point forward your tests create objects with a single call. Fields you care about in a specific test are passed as overrides; everything else fills in automatically with sensible, unique values.
In this article you will learn how factory_boy works from the ground up. We will cover creating basic factories, generating unique sequential values, using lazy attributes that depend on other fields, building related objects with sub-factories, creating batches of test data, and using factory_boy with Django ORM and SQLAlchemy. By the end you will be able to write clean, expressive tests where the setup code tells the reader exactly what matters and nothing more.
How To Use factory_boy: Quick Example
The fastest way to understand factory_boy is to see a working example. Here is a factory that generates User objects with unique, realistic field values — no database or ORM required for this first example.
# quick_example.py
import factory
from dataclasses import dataclass
@dataclass
class User:
id: int
username: str
email: str
is_active: bool = True
class UserFactory(factory.Factory):
class Meta:
model = User
id = factory.Sequence(lambda n: n + 1)
username = factory.Sequence(lambda n: f"user_{n}")
email = factory.LazyAttribute(lambda obj: f"{obj.username}@example.com")
is_active = True
# Create one user
user = UserFactory()
print(f"User: {user.username}, Email: {user.email}, Active: {user.is_active}")
# Override any field you care about
admin = UserFactory(username="admin", is_active=True)
print(f"Admin: {admin.username}, Email: {admin.email}")
# Create three users at once
users = UserFactory.create_batch(3)
for u in users:
print(f" - {u.username} ({u.email})")
Output:
User: user_0, Email: user_0@example.com, Active: True
Admin: admin, Email: admin@example.com
- user_1 (user_1@example.com)
- user_2 (user_2@example.com)
- user_3 (user_3@example.com)
Notice that email uses a LazyAttribute — it reads the username that was just generated (or overridden) and builds the email from it. Even when we pass username="admin", the email automatically becomes admin@example.com. This is the core power of factory_boy: fields can be related to each other, and overrides cascade naturally. The sections below dig into every feature you just saw and several more.
What Is factory_boy and Why Use It?
factory_boy is a test fixture replacement library for Python. A “fixture” in testing means test data — the objects, records, and state your tests need to exist before they can run. factory_boy lets you define a factory class once that describes how to build a valid instance of any Python object, and then call that factory in your tests to get realistic, unique instances on demand.
Think of it like a cookie cutter. Instead of shaping each cookie by hand (manually constructing every User object field by field in every test), you stamp out identical-but-customizable shapes with a single press. The cutter knows the shape; you just choose the flavor.
| Approach | Setup per test | Uniqueness | Field relationships | ORM support |
|---|---|---|---|---|
| Manual object construction | 10-30 lines | Manual / copy-paste | None | Manual |
| Hardcoded test fixtures (JSON/YAML) | Zero lines (but brittle) | Fixed | None | None |
| factory_boy | 1 line | Automatic (Sequence) | LazyAttribute | Django, SQLAlchemy, Mongoengine |
factory_boy works with plain Python objects (dataclasses, attrs classes, or any Python class), Django models, SQLAlchemy models, and MongoDB with Mongoengine — so it fits into almost any Python project. The rest of this article walks through the features you will use most often.
Installation and Setup
Install factory_boy from PyPI. The package name uses a hyphen but the import uses an underscore:
# terminal
pip install factory-boy
Output:
Successfully installed factory-boy-3.x.x
If you plan to use factory_boy with Django or SQLAlchemy, those packages must also be installed, but factory_boy itself has no mandatory dependencies. For plain Python objects, the install above is everything you need.
Generating Unique Values with Sequence
One of the most common problems with test data is uniqueness. If your database has a unique constraint on email, creating two users with the same email crashes your test suite — even when the email is not what you are testing at all. factory_boy’s factory.Sequence solves this by incrementing a counter each time an object is built.
# sequence_demo.py
import factory
from dataclasses import dataclass
@dataclass
class Product:
id: int
name: str
sku: str
price: float
class ProductFactory(factory.Factory):
class Meta:
model = Product
id = factory.Sequence(lambda n: n + 1)
name = factory.Sequence(lambda n: f"Product {n}")
sku = factory.Sequence(lambda n: f"SKU-{n:05d}") # zero-padded: SKU-00001
price = 9.99 # static default -- can always be overridden
p1 = ProductFactory()
p2 = ProductFactory()
p3 = ProductFactory(price=49.99) # override only the price
print(f"{p1.sku} {p1.name} ${p1.price}")
print(f"{p2.sku} {p2.name} ${p2.price}")
print(f"{p3.sku} {p3.name} ${p3.price}")
Output:
SKU-00001 Product 1 $9.99
SKU-00002 Product 2 $9.99
SKU-00003 Product 3 $49.99
Each call to ProductFactory() increments the counter, so name and sku are always unique across the entire test run. Notice that price is set to a static default of 9.99. When a specific test cares about price (like a discount calculation test), it passes price=49.99 as an override and everything else stays auto-generated. This pattern — auto-generate what does not matter, override what does — is the core ergonomic benefit of factory_boy.
Deriving Fields with LazyAttribute
Many model fields are not independent — an email address is derived from a username, a full name is composed from first and last names, a URL slug is derived from a title. factory.LazyAttribute lets you express these relationships so that overrides cascade automatically.
# lazy_attributes.py
import factory
from dataclasses import dataclass
@dataclass
class Author:
first_name: str
last_name: str
email: str
display_name: str
slug: str
class AuthorFactory(factory.Factory):
class Meta:
model = Author
first_name = factory.Faker("first_name")
last_name = factory.Faker("last_name")
email = factory.LazyAttribute(
lambda obj: f"{obj.first_name.lower()}.{obj.last_name.lower()}@blog.com"
)
display_name = factory.LazyAttribute(
lambda obj: f"{obj.first_name} {obj.last_name}"
)
slug = factory.LazyAttribute(
lambda obj: f"{obj.first_name.lower()}-{obj.last_name.lower()}"
)
a1 = AuthorFactory()
a2 = AuthorFactory(first_name="Jane", last_name="Smith") # all derived fields update
print(f"Author 1: {a1.display_name} | {a1.email} | {a1.slug}")
print(f"Author 2: {a2.display_name} | {a2.email} | {a2.slug}")
Output:
Author 1: Marcus Williams | marcus.williams@blog.com | marcus-williams
Author 2: Jane Smith | jane.smith@blog.com | jane-smith
When we override first_name and last_name, the email, display_name, and slug all update accordingly — because LazyAttribute re-evaluates using the actual values on the object being built, not the defaults. Also notice factory.Faker("first_name"): factory_boy integrates directly with the Faker library to generate realistic random data for names, addresses, phone numbers, dates, and dozens of other field types.
Building Related Objects with SubFactory
Real models rarely exist in isolation. A Post belongs to an Author; an OrderLine belongs to an Order that belongs to a Customer. factory_boy handles these relationships with factory.SubFactory, which automatically creates a parent object when you create a child — unless you pass one in explicitly.
# subfactory_demo.py
import factory
from dataclasses import dataclass, field
from typing import List
@dataclass
class Author:
id: int
name: str
email: str
@dataclass
class Post:
id: int
title: str
author: Author
published: bool = False
class AuthorFactory(factory.Factory):
class Meta:
model = Author
id = factory.Sequence(lambda n: n + 1)
name = factory.Faker("name")
email = factory.LazyAttribute(lambda obj: f"{obj.name.lower().replace(' ', '.')}@site.com")
class PostFactory(factory.Factory):
class Meta:
model = Post
id = factory.Sequence(lambda n: n + 1)
title = factory.Sequence(lambda n: f"Post Title {n}")
author = factory.SubFactory(AuthorFactory) # creates an Author automatically
published = False
# Creating a post also creates its author
post1 = PostFactory()
print(f"Post: '{post1.title}' by {post1.author.name}")
# Reuse an existing author across multiple posts
shared_author = AuthorFactory(name="Alice Johnson")
post2 = PostFactory(author=shared_author, published=True)
post3 = PostFactory(author=shared_author)
print(f"Both by {shared_author.name}: '{post2.title}' and '{post3.title}'")
print(f"Post 2 published: {post2.published} | Post 3 published: {post3.published}")
Output:
Post: 'Post Title 1' by Marcus Brown
Both by Alice Johnson: 'Post Title 2' and 'Post Title 3'
Post 2 published: True | Post 3 published: False
When you call PostFactory(), factory_boy sees that author is a SubFactory and automatically calls AuthorFactory() to produce a valid Author. If a test needs two posts from the same author (for example, testing an author’s post count), create the author once and pass it to both post factories. The test setup reads clearly: “these two posts share one author” — and you did not have to repeat a single field.
Creating Multiple Objects with create_batch and build_batch
Tests for pagination, filtering, or aggregation often need multiple records. Instead of calling the factory in a loop, factory_boy provides create_batch and build_batch for creating lists of objects in one call. You can also pass overrides that apply to every object in the batch.
# batch_demo.py
import factory
from dataclasses import dataclass
@dataclass
class Article:
id: int
title: str
category: str
views: int
class ArticleFactory(factory.Factory):
class Meta:
model = Article
id = factory.Sequence(lambda n: n + 1)
title = factory.Sequence(lambda n: f"Article {n}: {factory.Faker._get_faker().sentence(nb_words=4)}")
category = factory.Iterator(["Python", "Django", "Testing", "Data Science"])
views = factory.Faker("random_int", min=0, max=10000)
# Create 5 articles, all in the "Testing" category
testing_articles = ArticleFactory.build_batch(5, category="Testing")
print("Testing articles:")
for a in testing_articles:
print(f" [{a.id}] {a.title} -- {a.views} views")
# Use Iterator to cycle through categories automatically
mixed_articles = ArticleFactory.build_batch(4)
print("\nMixed categories:")
for a in mixed_articles:
print(f" [{a.id}] {a.category}: {a.title[:40]}...")
Output:
Testing articles:
[1] Article 1: ... -- 3241 views
[2] Article 2: ... -- 7812 views
[3] Article 3: ... -- 521 views
[4] Article 4: ... -- 9103 views
[5] Article 5: ... -- 2277 views
Mixed categories:
[6] Python: Article 6: ...
[7] Django: Article 7: ...
[8] Testing: Article 8: ...
[9] Data Science: Article 9: ...
build_batch creates objects without saving them to a database — ideal for unit tests. create_batch calls .save()` (Django) or adds to the session (SQLAlchemy). factory.Iterator cycles through a list of values, so each successive object in the batch gets the next value, then wraps around. This is cleaner than hardcoding every combination manually.
Using factory_boy with Django ORM
When your project uses Django models, swap factory.Factory for factory.django.DjangoModelFactory. This subclass knows how to call Model.objects.create() instead of Model(), so objects are persisted to the test database automatically when you call the factory.
# factories.py (in your Django app's test directory)
import factory
from factory.django import DjangoModelFactory
from myapp.models import User, Profile
class UserFactory(DjangoModelFactory):
class Meta:
model = User
username = factory.Sequence(lambda n: f"testuser_{n}")
email = factory.LazyAttribute(lambda obj: f"{obj.username}@example.com")
first_name = factory.Faker("first_name")
last_name = factory.Faker("last_name")
is_active = True
class ProfileFactory(DjangoModelFactory):
class Meta:
model = Profile
user = factory.SubFactory(UserFactory)
bio = factory.Faker("paragraph", nb_sentences=2)
location = factory.Faker("city")
# tests.py -- using the factories in a Django test case
from django.test import TestCase
from .factories import UserFactory, ProfileFactory
class ProfileViewTests(TestCase):
def test_profile_page_shows_bio(self):
# One line creates the user AND the profile
profile = ProfileFactory(bio="Expert Python programmer.")
response = self.client.get(f"/profile/{profile.user.username}/")
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Expert Python programmer.")
def test_user_listing_shows_all_active_users(self):
# Create 5 active users and 2 inactive users
UserFactory.create_batch(5, is_active=True)
UserFactory.create_batch(2, is_active=False)
response = self.client.get("/users/")
self.assertEqual(len(response.context["users"]), 5)
Output (pytest or Django test runner):
..
----------------------------------------------------------------------
Ran 2 tests in 0.342s
OK
The test for the profile page only cares about one thing: whether the bio shows up on the page. With factory_boy, that is the only field we specify. The username, email, and user account are created automatically and the test is completely self-documenting: "create a profile with this bio, then check the page shows it." Without factory_boy, this test would start with 10 lines of User.objects.create(username=..., email=..., password=..., ...) before you even get to the assertion.
Using factory_boy with SQLAlchemy
For SQLAlchemy projects, factory_boy provides factory.alchemy.SQLAlchemyModelFactory. The key difference is that you must supply the SQLAlchemy session so factory_boy knows where to add the created objects.
# sqlalchemy_factories.py
import factory
from factory.alchemy import SQLAlchemyModelFactory
from sqlalchemy import Column, Integer, String, Boolean, ForeignKey, create_engine
from sqlalchemy.orm import relationship, sessionmaker, declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
username = Column(String, unique=True)
email = Column(String, unique=True)
is_active = Column(Boolean, default=True)
# Create an in-memory SQLite database for testing
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
class UserFactory(SQLAlchemyModelFactory):
class Meta:
model = User
sqlalchemy_session = session # the session to use
sqlalchemy_session_persistence = "commit" # auto-commit after create
id = factory.Sequence(lambda n: n + 1)
username = factory.Sequence(lambda n: f"user_{n}")
email = factory.LazyAttribute(lambda obj: f"{obj.username}@test.com")
is_active = True
# Create objects -- they are persisted to the in-memory DB
u1 = UserFactory()
u2 = UserFactory(is_active=False)
# Query them back from the DB to confirm persistence
all_users = session.query(User).all()
active_users = session.query(User).filter_by(is_active=True).all()
print(f"Total users: {len(all_users)}")
print(f"Active users: {len(active_users)}")
for u in all_users:
print(f" {u.username} active={u.is_active}")
Output:
Total users: 2
Active users: 1
user_1 active=True
user_2 active=False
The SQLAlchemy integration is straightforward once the session is wired up. A common pattern in real test suites is to configure sqlalchemy_session at the base factory level so all child factories share the same session, and then use a pytest fixture to create a fresh session per test and update the factory's session reference at the start of each test. This ensures clean isolation between tests.
Real-Life Example: E-Commerce Order Test Suite
Let us put everything together with a realistic scenario: an e-commerce application where tests need to verify order total calculations, product inventory checks, and customer history. Without factory_boy, every test would require 30-50 lines of setup. With it, each test is three to five lines.
# ecommerce_factories.py
import factory
from dataclasses import dataclass, field
from typing import List
@dataclass
class Customer:
id: int
name: str
email: str
is_premium: bool = False
@dataclass
class Product:
id: int
name: str
sku: str
price: float
stock: int = 100
@dataclass
class OrderLine:
product: Product
quantity: int
@property
def subtotal(self):
return self.product.price * self.quantity
@dataclass
class Order:
id: int
customer: Customer
lines: List[OrderLine] = field(default_factory=list)
@property
def total(self):
return sum(line.subtotal for line in self.lines)
def add_line(self, product, quantity):
self.lines.append(OrderLine(product=product, quantity=quantity))
class CustomerFactory(factory.Factory):
class Meta:
model = Customer
id = factory.Sequence(lambda n: n + 1)
name = factory.Faker("name")
email = factory.LazyAttribute(lambda obj: f"{obj.name.lower().replace(' ', '.')}@shop.com")
is_premium = False
class ProductFactory(factory.Factory):
class Meta:
model = Product
id = factory.Sequence(lambda n: n + 1)
name = factory.Sequence(lambda n: f"Widget Model {n}")
sku = factory.Sequence(lambda n: f"WGT-{n:04d}")
price = factory.Faker("pyfloat", min_value=1.0, max_value=500.0, right_digits=2)
stock = 100
class OrderFactory(factory.Factory):
class Meta:
model = Order
id = factory.Sequence(lambda n: n + 1)
customer = factory.SubFactory(CustomerFactory)
# --- Tests using the factories ---
import unittest
class TestOrderTotals(unittest.TestCase):
def test_single_line_order_total(self):
product = ProductFactory(price=25.00)
order = OrderFactory()
order.add_line(product, quantity=3)
self.assertAlmostEqual(order.total, 75.00)
def test_multi_line_order_total(self):
p1 = ProductFactory(price=10.00)
p2 = ProductFactory(price=5.50)
order = OrderFactory()
order.add_line(p1, quantity=2)
order.add_line(p2, quantity=4)
# 20.00 + 22.00 = 42.00
self.assertAlmostEqual(order.total, 42.00)
def test_premium_customer_flag(self):
premium_customer = CustomerFactory(is_premium=True)
order = OrderFactory(customer=premium_customer)
self.assertTrue(order.customer.is_premium)
def test_out_of_stock_product(self):
out_of_stock = ProductFactory(stock=0)
self.assertEqual(out_of_stock.stock, 0)
if __name__ == "__main__":
unittest.main(verbosity=2)
Output:
test_multi_line_order_total (__main__.TestOrderTotals) ... ok
test_out_of_stock_product (__main__.TestOrderTotals) ... ok
test_premium_customer_flag (__main__.TestOrderTotals) ... ok
test_single_line_order_total (__main__.TestOrderTotals) ... ok
----------------------------------------------------------------------
Ran 4 tests in 0.012s
OK
Each test is a focused, self-documenting statement. test_single_line_order_total tells you exactly what it cares about -- a product at $25.00 multiplied by 3 -- and nothing else. The customer, SKU, product name, and every other field that does not matter to this specific calculation are handled automatically. To extend this example in your own projects, try adding a DiscountFactory with a SubFactory(CustomerFactory) to test premium discount logic, or a factory.Trait to define a "digital_product" variant with stock=-1 meaning unlimited.
Frequently Asked Questions
What is the difference between build() and create()?
build() instantiates the Python object in memory without touching any database. create() also persists the object -- by calling Model.objects.create() in Django or adding to the SQLAlchemy session and committing. For unit tests that do not need database access, use build() -- it is faster, requires no database setup, and avoids transaction overhead. Use create() only when a test genuinely needs to query the database or test ORM behavior.
How do I reset the sequence counter between tests?
factory_boy's Sequence counter is global per factory class, so it keeps incrementing across tests in the same run. If you need reproducible IDs, call UserFactory.reset_sequence(0) at the start of a test or in a setUp method. In Django test cases, you can also override _meta.reset_sequence = True at the factory level. For most tests this is unnecessary since you are testing behavior, not specific ID values -- but it matters if you are testing URL routing based on IDs or comparing fixtures against expected output.
Can I use custom Faker providers for domain-specific data?
Yes. Faker has over 100 built-in providers (factory.Faker("ipv4_private"), factory.Faker("isbn13"), factory.Faker("credit_card_number"), etc.) and you can register your own. For domain-specific data like product categories or country codes, factory.Iterator(["Electronics", "Clothing", "Books"]) is often simpler than a custom provider. If a field needs logic that neither Sequence nor LazyAttribute can express cleanly, use factory.LazyFunction(my_custom_function) to call an arbitrary callable with no arguments.
What are Traits and when should I use them?
A Trait is a named bundle of field overrides that you can apply to a factory call with a keyword argument. They are ideal when you find yourself passing the same group of overrides repeatedly. For example, instead of writing UserFactory(is_active=False, verified=False, login_count=0) in every test that needs an inactive user, define a @factory.Trait called inactive that sets all three fields, then call UserFactory(inactive=True). Traits make intent clear and keep override logic in one place.
How do I avoid hitting the database in unit tests when using DjangoModelFactory?
Use UserFactory.build() instead of UserFactory() (which calls create() by default for DjangoModelFactory). build() constructs the object in memory without calling .save(). If your test calls view code or serializers that reference the object but does not query the database, build() gives you a valid Python object at no database cost. For tests that mock the ORM layer entirely (with unittest.mock.patch), build() is always the right choice.
Conclusion
factory_boy eliminates the single most tedious part of writing Python tests: setting up valid, realistic test data. You have seen how factory.Sequence ensures unique field values across every object ever built, how factory.LazyAttribute lets fields depend on each other so overrides cascade correctly, how factory.SubFactory builds related objects automatically, and how build_batch creates lists of objects in one call. The Django and SQLAlchemy integrations wire the same patterns directly to your ORM so created objects land in the test database with no extra code.
The real-life example showed the payoff: four tests, each between three and five lines, each perfectly self-documenting. Every field that a test explicitly sets is a field that matters to that test's assertion. Everything else is handled by the factory. Try taking one of your existing test files, identifying the tests with the most setup code, and replacing that setup with a factory. The reduction in noise is immediate.
For advanced features like post-generation hooks (@factory.post_generation), exclusions, and custom factory inheritance, see the official factory_boy documentation.