Factory Boy centralizes creation of valid test objects. Instead of repeating long constructors, each test overrides only the field related to the behavior it verifies.
Build a deterministic factory
from dataclasses import dataclass
import factory
@dataclass
class User:
name: str
email: str
active: bool = True
class UserFactory(factory.Factory):
class Meta:
model = User
name = factory.Sequence(lambda n: f"User {n}")
email = factory.LazyAttribute(
lambda obj: obj.name.lower().replace(" ", ".") + "@example.com"
)
In a test, UserFactory(active=False) makes the relevant variation obvious. Use SubFactory for relationships and traits for recurring states, but do not hide a huge object graph behind an innocent-looking call.
A factory should produce the smallest valid object. If every creation writes to a database, unit tests become slow and difficult to isolate. Choose the build or persistence strategy intentionally and clean transactions between cases.
Avoid unseeded randomness. A test that fails only for one generated name is hard to diagnose. Combine factories with pytest fixtures for dependencies with a lifecycle, while keeping factories focused on data.
The official Factory Boy documentation, accessed July 22, 2026, covers declarations, associations, traits, strategies, and ORM integrations.
Install the package and choose the right base
Install Factory Boy in the development dependency group:
python -m pip install factory-boy
Use factory.Factory for plain classes and dataclasses. Framework integrations such as DjangoModelFactory and SQLAlchemyModelFactory understand persistence conventions, but they also make database access easy to trigger. Select the base according to what the test needs, not merely because the production project uses an ORM.
A useful factory defines valid, unsurprising defaults. It should not encode every possible state of the model. The test supplies the value that explains its scenario:
def test_inactive_user_cannot_sign_in() -> None:
user = UserFactory(active=False)
assert authenticate(user) is False
The override communicates intent immediately. A hand written User(...) with ten unrelated fields would make the important difference harder to find.
Understand declarations and evaluation
Sequence creates deterministic unique values within a process. LazyAttribute calculates a field from other fields on the same object, while LazyFunction calls a function without receiving the object. Use Iterator for a controlled cycle of values.
from datetime import UTC, datetime
class UserFactory(factory.Factory):
class Meta:
model = User
username = factory.Sequence(lambda n: f"user-{n}")
email = factory.LazyAttribute(lambda obj: f"{obj.username}@example.test")
created_at = factory.LazyFunction(lambda: datetime.now(UTC))
role = factory.Iterator(["reader", "editor"])
Time based defaults can still introduce nondeterminism. If behavior depends on the timestamp, override it with a fixed value or freeze the clock in the test. Use reserved domains such as example.test so generated addresses cannot accidentally target real recipients.
Model relationships with SubFactory
SubFactory expresses ownership or a required relation. RelatedFactory creates an object after the main object when the reverse relation is the useful direction.
@dataclass
class Order:
customer: User
total_cents: int
class OrderFactory(factory.Factory):
class Meta:
model = Order
customer = factory.SubFactory(UserFactory)
total_cents = 2500
OrderFactory(customer__active=False) overrides a field inside the subfactory. This double underscore syntax is powerful, but deep chains are a warning. If creating one order silently creates an organization, permissions, subscriptions, and messages, tests become expensive and failures become obscure. Keep the default graph small and create optional relations explicitly.
Represent recurring states with traits
Traits group fields that together describe a meaningful state:
class SubscriptionFactory(factory.Factory):
class Meta:
model = Subscription
active = True
cancelled_at = None
class Params:
cancelled = factory.Trait(
active=False,
cancelled_at=datetime(2026, 1, 15, tzinfo=UTC),
)
SubscriptionFactory(cancelled=True) is clearer than repeating two coordinated overrides. Choose domain terms for traits, not vague names such as special. Avoid contradictory combinations by keeping traits focused and validating impossible states in the model.
Parameters can also derive declarations without becoming model fields. They are useful for a small number of supported variants. If dozens of traits interact, separate factories or builder functions may communicate the domain better.
Build, create, and control persistence
Factory Boy has build and create strategies. For plain Factory, calling the class builds the object. ORM integrations commonly make create() persist and build() stay in memory.
Use in-memory objects for unit tests whenever the behavior does not require a database. In integration tests, persistence is appropriate, but transaction rollback and cleanup belong to the test framework. A factory is not a replacement for isolation.
For batches, build_batch(3) and create_batch(3) are concise. Large batches can hide performance problems, so request only the number required by the scenario. If a query should handle 10,000 rows, a focused performance fixture or data loader may be more suitable than constructing a huge object graph through factories.
Use post-generation hooks carefully
PostGeneration and post_generation handle values that can only be applied after construction, such as many-to-many relations in an ORM. Hooks make sense for framework lifecycle requirements, but implicit side effects should remain visible.
class TeamFactory(factory.Factory):
class Meta:
model = Team
name = factory.Sequence(lambda n: f"Team {n}")
@factory.post_generation
def members(self, create, extracted, **kwargs):
if extracted:
for member in extracted:
self.add_member(member)
A caller can then write TeamFactory(members=[ana, sam]). Document whether the hook needs persistence and what happens under build(). Avoid network requests, email, and background jobs in factory hooks. Patch those effects or create objects through a lower level interface designed for tests.
Integrate factories with pytest
A factory creates data; a pytest fixture manages lifecycle. A fixture may expose the factory directly or return a small callable with test specific defaults:
import pytest
@pytest.fixture
def user_factory():
created: list[User] = []
def make_user(**overrides) -> User:
user = UserFactory(**overrides)
created.append(user)
return user
yield make_user
created.clear()
For database integrations, rely on the transaction fixtures provided by the framework. Do not add manual deletion that conflicts with rollback. The guide to pytest parametrization explains how to run the same behavior with a small set of meaningful factory overrides.
Keep randomness reproducible
Factory Boy integrates with Faker and fuzzy declarations. Realistic variety can reveal assumptions about Unicode, maximum lengths, or optional fields, but uncontrolled randomness produces failures that are difficult to repeat. Prefer deterministic defaults for ordinary tests.
When random generation has a purpose, seed it and report the seed on failure. Minimize the failing value before adding it as a permanent regression case. Property based testing may be a better tool when the goal is systematic generation and shrinking.
Maintain factories as production models evolve
Keep factories close to the test suite and review them when required fields or validation change. A default that bypasses a new business rule can let tests build states that production rejects. Conversely, forcing every new optional production field into all factories creates needless churn.
Avoid importing application services into factory modules. Factories should describe construction, not orchestrate use cases. If the only way to create a valid aggregate is through a domain service, call that service in integration tests and reserve factories for its input objects.
Review checklist
Check that the factory creates the smallest valid object, defaults are deterministic, addresses use safe domains, and one call does not create a surprising graph. Confirm whether the chosen strategy writes to the database, who cleans persisted rows, and whether hooks trigger side effects.
Factories improve tests when they make the relevant difference obvious. If a reader can see OrderFactory(total_cents=0) and immediately understand the boundary under test, the abstraction is helping. If understanding requires tracing several traits and hooks, simplify it.