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.