iTestData

Factory Patterns: factory_boy, FactoryBot, Mimesis

Most test suites don't break because the code is wrong — they break because the data is fragile. A hardcoded user fixture with a created_at from 2019, a product record missing a required FK, an order total that violates a business invariant nobody wrote down: these are the real sources of CI noise. We spend cycles chasing flaky tests when we should be fixing the data layer that makes them possible.

Factory patterns solve this at the source. Instead of maintaining static fixtures that drift from production schemas, you define how to construct a valid object — and let the factory generate fresh, schema-conformant instances on demand. The pattern is mature: FactoryBot has been the Rails standard for over a decade; factory_boy brought it to Python; Mimesis offers a high-throughput alternative when you need volume over relational fidelity.

By the end of this article you'll know when to reach for each tool, how to compose factories with realistic relational graphs, and which patterns cause subtle data bugs even in well-maintained test suites.

Modern Test Automation with AI and BDD

Practical guides for building smarter test frameworks, pipelines, and automation strategies.

Learn more

What Factory Patterns Actually Do in a Test Data Architecture

A factory is a callable that returns a fully-constructed, valid domain object — not a raw dict, not a SQL insert template, not a YAML blob. The factory owns the construction logic: default values, trait overrides, sub-factory relationships, and post-generation hooks. The test only specifies what's relevant to the assertion; everything else is delegated. This separation is what makes factory-based suites resilient to schema changes — you update one factory, not 300 fixture files.

In a modern test architecture, factories sit between your domain model and your test layer. They're not a replacement for contract testing (Pact), schema validation (JSON Schema 2020-12, Great Expectations), or property-based testing (Hypothesis). They're the data construction layer — the thing that produces the inputs those other tools exercise. factory_boy integrates directly with SQLAlchemy and Django ORM, persisting objects to Postgres in the same transaction your test rolls back. FactoryBot does the same for ActiveRecord. Mimesis operates in-memory only, which makes it fast but also means you wire persistence yourself.

Building Relational Factories with factory_boy and Mimesis

Start with a concrete domain: an e-commerce order that belongs to a user and contains line items. With factory_boy and SQLAlchemy, the factory graph mirrors the ORM graph:

import factory
from factory.alchemy import SQLAlchemyModelFactory
from myapp.models import User, Order, LineItem
from myapp.db import Session

class UserFactory(SQLAlchemyModelFactory):
    class Meta:
        model = User
        sqlalchemy_session = Session

    email = factory.Faker("email")
    name  = factory.Faker("name")
    tier  = factory.Iterator(["free", "pro", "enterprise"])

class OrderFactory(SQLAlchemyModelFactory):
    class Meta:
        model = Order
        sqlalchemy_session = Session

    user        = factory.SubFactory(UserFactory)
    status      = "pending"
    total_cents = factory.Faker("random_int", min=100, max=99999)

    class Params:
        completed = factory.Trait(
            status="completed",
            total_cents=factory.Faker("random_int", min=500, max=99999),
        )

class LineItemFactory(SQLAlchemyModelFactory):
    class Meta:
        model = LineItem
        sqlalchemy_session = Session

    order    = factory.SubFactory(OrderFactory)
    sku      = factory.Faker("bothify", text="SKU-####-??")
    quantity = factory.Faker("random_int", min=1, max=10)
    price_cents = factory.Faker("random_int", min=50, max=5000)

factory.Trait is the pattern most teams underuse. Instead of creating separate CompletedOrderFactory subclasses, traits let you compose state: OrderFactory(completed=True) gives you a completed order without duplicating defaults. This keeps the factory count flat as your domain grows.

For volume scenarios — seeding a Postgres instance with 500k orders for a load test, or generating a Kafka topic's worth of events — Mimesis outperforms factory_boy significantly. Mimesis 11.x generates ~2M records/second in-process because it skips ORM overhead entirely. The trade-off: you own the relational consistency.

from mimesis import Person, Finance, Numeric
from mimesis.enums import Gender
import json, uuid

person  = Person("en")
finance = Finance("en")
numeric = Numeric()

def make_order_event(user_id: str) -> dict:
    return {
        "order_id":   str(uuid.uuid4()),
        "user_id":    user_id,
        "email":      person.email(),
        "total":      round(numeric.float_number(start=1.0, end=999.99), 2),
        "currency":   finance.currency_iso_code(),
        "created_at": "2024-06-01T00:00:00Z",
    }

events = [make_order_event(str(uuid.uuid4())) for _ in range(500_000)]

Generating 500k order events with this approach takes under 4 seconds on a standard laptop — compared to ~14 minutes using SQLAlchemy bulk inserts with factory_boy's create_batch. Use Mimesis when you need raw throughput for load testing or ML training data; use factory_boy when you need ORM-integrated, transactionally isolated records for integration tests. FactoryBot on the Rails side offers the same ORM integration via create, build, and build_stubbed strategies — build_stubbed in particular avoids DB hits entirely, cutting suite time on large models by 40–60% in practice.

Factory Pitfalls That Bite Senior Engineers Too

Over-creating via SubFactory chains. Every SubFactory call issues an INSERT by default. A test that needs one LineItem can silently create a User, an Address, a PaymentMethod, and an Order as side effects. In a suite of 2,000 tests this becomes thousands of unnecessary writes, and the slowdown is invisible until CI starts timing out. Audit your factories with SQLAlchemy's event.listen(engine, "before_cursor_execute", ...) to count statements per test — the numbers are usually surprising. Fix it by passing pre-built instances explicitly: OrderFactory(user=existing_user).

Shared mutable state between factory instances. Defining a mutable default — a list, a dict — at the class level rather than via factory.LazyAttribute or factory.LazyFunction means all instances share the same object. This is the Python default-argument trap, and factory_boy doesn't protect you from it. The symptom is a test that passes in isolation but fails in suite order — a classic data-mutation flakiness pattern. Always use factory.LazyFunction(list) for mutable defaults, never a bare [].

Myths About Factories That Lead to Brittle Test Data

Myth: randomness equals coverage. Factories that use Faker for every field feel thorough but aren't. Random strings don't exercise boundary conditions; random integers don't hit zero, max-int, or negative values unless you specify them. Factories give you valid-by-default objects — they don't replace Hypothesis for property-based boundary exploration. Use factories to construct the shape of your data; use Hypothesis or explicit edge-case traits to push the values to boundaries. These tools compose: @given(st.builds(UserFactory)) works.

Myth: a factory that matches the ORM model is enough. Your ORM model and your API contract are not the same thing. A factory that produces valid SQLAlchemy objects can still generate JSON that fails JSON Schema 2020-12 validation at the API boundary — especially after a migration adds a non-nullable column with an ORM-level default that isn't reflected in the schema. Wire your factories to schema validation as a post-generation check, or run Schemathesis against endpoints seeded with factory data. The factory is the floor, not the ceiling, of data correctness.

Factory patterns are the highest-leverage investment in test data maintainability for teams running integration or service-level tests at scale. Start by auditing one slow test file: count the fixture files it touches, then replace them with a factory graph. Measure the before/after INSERT count and suite time. From there, layer in Mimesis for volume scenarios and Hypothesis for boundary coverage. The SQLAlchemy factory_boy docs and the FactoryBot handbook both cover advanced sequences and callbacks worth reading once you've outgrown basic SubFactory composition.

Note: This article is for informational purposes only and is not a substitute for professional advice. If you need guidance on specific situations described in this article, consider consulting a qualified professional.

Understanding how systems actually work is the first step toward navigating them effectively.

Browse all articles