factory_boy generates test objects with realistic data. pip install factory-boy. import factory. Define: class UserFactory(factory.Factory): class Meta: model = User; name = factory.Sequence(lambda n: f"user-{n}"); email = factory.LazyAttribute(lambda o: f"{o.name}@example.com"). Create: user = UserFactory(). Override: UserFactory(name="Alice"). Multiple: UserFactory.create_batch(5). Build (no save): UserFactory.build(). Stub (no model): UserFactory.stub(). SubFactory: class OrderFactory: user = factory.SubFactory(UserFactory). LazyAttribute: factory.LazyAttribute(lambda o: f"{o.first_name}.{o.last_name}@co.com"). LazyFunction: factory.LazyFunction(lambda: datetime.utcnow()). Sequence: factory.Sequence(lambda n: f"user{n}@example.com"). Faker: factory.Faker("email"), factory.Faker("first_name"). factory.Faker("random_int", min=1, max=100). Trait: class Meta: ... ; class Params: admin = factory.Trait(role="admin", is_staff=True) — UserFactory(admin=True). PostGeneration: @factory.post_generation def password(self, create, extracted, **kwargs): self.set_password(extracted or "password123"). RelatedFactory: class UserFactory: profile = factory.RelatedFactory(ProfileFactory, factory_related_name="user"). create_batch(3, is_active=True). Django: class UserFactory(factory.django.DjangoModelFactory): class Meta: model = "auth.User"; django_get_or_create = ("username",). SQLAlchemy: class Meta: model = User; sqlalchemy_session = session. class Meta: exclude = ["_password"] — exclude computed fields. DjangoOptions.skip_postgeneration_save = True. factory.Iterator(["a","b","c"]) — cycles through values. factory.Maybe("is_active", yes_declaration=..., no_declaration=...). Claude Code generates factory_boy factory hierarchies, trait patterns, and pytest fixture integration.
CLAUDE.md for factory_boy
## factory_boy Stack
- Version: factory-boy >= 3.3 | pip install factory-boy
- Base: factory.Factory (plain) | DjangoModelFactory | SQLAlchemyModelFactory
- Field: Sequence | LazyAttribute | LazyFunction | Faker | SubFactory
- Variants: Trait (class Params) | Maybe | Iterator
- Create: Factory() | .build() (no save) | .create_batch(n) | .stub()
- Hooks: @factory.post_generation | RelatedFactory for reverse FK
- Pytest: wrap factories in fixtures using Factory.create / build
factory_boy Test Factory Pipeline
# tests/factories.py — factory_boy definitions for test object generation
from __future__ import annotations
import decimal
import random
from datetime import datetime, timedelta, timezone
import factory
from factory import Faker, LazyAttribute, LazyFunction, Sequence, SubFactory
from factory.fuzzy import FuzzyChoice, FuzzyDecimal, FuzzyInteger, FuzzyText
# ── 0. Data models (plain Python — no ORM dependency for examples) ────────────
class Address:
def __init__(self, street, city, state, postal_code, country="US"):
self.street = street
self.city = city
self.state = state
self.postal_code = postal_code
self.country = country
class User:
def __init__(self, id, first_name, last_name, email, role,
is_active, created_at, address=None):
self.id = id
self.first_name = first_name
self.last_name = last_name
self.email = email
self.role = role
self.is_active = is_active
self.created_at = created_at
self.address = address
self._password_hash = ""
@property
def full_name(self):
return f"{self.first_name} {self.last_name}"
def set_password(self, raw: str) -> None:
self._password_hash = f"hashed:{raw}"
def check_password(self, raw: str) -> bool:
return self._password_hash == f"hashed:{raw}"
class Product:
def __init__(self, id, name, sku, price, stock, category, is_active):
self.id = id
self.name = name
self.sku = sku
self.price = price
self.stock = stock
self.category = category
self.is_active = is_active
class OrderLine:
def __init__(self, product, quantity, unit_price):
self.product = product
self.quantity = quantity
self.unit_price = unit_price
@property
def subtotal(self):
return self.quantity * self.unit_price
class Order:
def __init__(self, id, user, lines, status, created_at):
self.id = id
self.user = user
self.lines = lines
self.status = status
self.created_at = created_at
@property
def total(self):
return sum(line.subtotal for line in self.lines)
# ── 1. Address factory ────────────────────────────────────────────────────────
class AddressFactory(factory.Factory):
class Meta:
model = Address
street = Faker("street_address")
city = Faker("city")
state = Faker("state_abbr")
postal_code = Faker("postcode")
country = "US"
# ── 2. User factory with Sequence, LazyAttribute, SubFactory, Trait ───────────
class UserFactory(factory.Factory):
class Meta:
model = User
# Unique incrementing ID
id = Sequence(lambda n: n + 1)
# Realistic names from Faker
first_name = Faker("first_name")
last_name = Faker("last_name")
# Derived from other fields
email = LazyAttribute(
lambda o: f"{o.first_name.lower()}.{o.last_name.lower()}@example.com"
)
role = FuzzyChoice(["user", "moderator"])
is_active = True
created_at = LazyFunction(lambda: datetime.now(timezone.utc))
# Optional nested object
address = SubFactory(AddressFactory)
class Params:
# Trait: UserFactory(admin=True) → role="admin", is_active=True
admin = factory.Trait(
role="admin",
is_active=True,
)
# Trait: UserFactory(inactive=True)
inactive = factory.Trait(
is_active=False,
)
@factory.post_generation
def password(self, create, extracted, **kwargs):
"""
UserFactory(password="mypass") → hashes it.
UserFactory() → uses default "testpass123".
"""
raw = extracted or "testpass123"
self.set_password(raw)
# ── 3. Product factory with FuzzyDecimal and FuzzyInteger ────────────────────
CATEGORIES = ["Electronics", "Clothing", "Books", "Home & Garden", "Sports"]
class ProductFactory(factory.Factory):
class Meta:
model = Product
id = Sequence(lambda n: n + 100)
name = Faker("catch_phrase")
sku = LazyAttribute(lambda o: f"SKU-{abs(hash(o.name)) % 100000:05d}")
price = FuzzyDecimal(0.99, 499.99, precision=2)
stock = FuzzyInteger(0, 500)
category = FuzzyChoice(CATEGORIES)
is_active = True
class Params:
out_of_stock = factory.Trait(stock=0)
premium = factory.Trait(
price=FuzzyDecimal(200.00, 999.99, precision=2),
category="Electronics",
)
# ── 4. OrderLine and Order with RelatedFactory ────────────────────────────────
class OrderLineFactory(factory.Factory):
class Meta:
model = OrderLine
product = SubFactory(ProductFactory)
quantity = FuzzyInteger(1, 5)
unit_price = LazyAttribute(lambda o: o.product.price)
class OrderFactory(factory.Factory):
class Meta:
model = Order
exclude = ["_line_count"]
_line_count = 2 # internal parameter
id = Sequence(lambda n: n + 1000)
user = SubFactory(UserFactory)
status = FuzzyChoice(["pending", "processing", "shipped", "delivered"])
created_at = LazyFunction(lambda: datetime.now(timezone.utc))
@factory.lazy_attribute
def lines(self):
return OrderLineFactory.build_batch(self._line_count)
class Params:
large_order = factory.Trait(_line_count=10)
single_item = factory.Trait(_line_count=1)
# ── 5. Iterator and Maybe ─────────────────────────────────────────────────────
class RoundRobinUserFactory(factory.Factory):
"""Users cycle through roles: user → moderator → admin → user ..."""
class Meta:
model = User
id = Sequence(lambda n: n + 1)
first_name = Faker("first_name")
last_name = Faker("last_name")
email = LazyAttribute(lambda o: f"{o.first_name.lower()}@test.com")
role = factory.Iterator(["user", "moderator", "admin"])
is_active = True
created_at = LazyFunction(lambda: datetime.now(timezone.utc))
@factory.post_generation
def password(self, create, extracted, **kwargs):
self.set_password(extracted or "testpass123")
# ── Demo functions ─────────────────────────────────────────────────────────────
def demo_factories():
print("=== UserFactory ===")
alice = UserFactory(first_name="Alice", last_name="Smith")
print(f" {alice.full_name} <{alice.email}> | role={alice.role}")
print(f" check_password: {alice.check_password('testpass123')}")
admin = UserFactory(admin=True)
print(f"\n Admin: {admin.full_name} | role={admin.role}")
print("\n=== ProductFactory ===")
products = ProductFactory.build_batch(3)
for p in products:
print(f" [{p.sku}] {p.name[:40]!r} ${p.price} stock={p.stock}")
premium = ProductFactory(premium=True)
print(f"\n Premium: {premium.name!r} ${premium.price}")
print("\n=== OrderFactory ===")
order = OrderFactory()
print(f" Order #{order.id} for {order.user.full_name}")
print(f" {len(order.lines)} lines | total=${order.total:.2f} | status={order.status}")
big_order = OrderFactory(large_order=True)
print(f"\n Large order: {len(big_order.lines)} lines | total=${big_order.total:.2f}")
print("\n=== Round-robin roles ===")
users = [RoundRobinUserFactory() for _ in range(6)]
for u in users:
print(f" {u.first_name:12} role={u.role}")
# ── pytest fixture integration ────────────────────────────────────────────────
# conftest.py pattern:
CONFTEST_EXAMPLE = """
import pytest
from tests.factories import UserFactory, ProductFactory, OrderFactory
@pytest.fixture
def user():
return UserFactory()
@pytest.fixture
def admin_user():
return UserFactory(admin=True)
@pytest.fixture
def product():
return ProductFactory()
@pytest.fixture
def order(user, product):
# Pass existing objects to avoid creating duplicates
return OrderFactory(user=user)
@pytest.fixture
def product_catalog():
return ProductFactory.build_batch(10)
"""
if __name__ == "__main__":
demo_factories()
For the pytest fixtures with hard-coded dicts alternative — hand-writing {"id": 1, "name": "Alice", "email": "[email protected]", "role": "user", ...} in every test file means 20 fields to maintain across 50 test files when a model changes, while UserFactory() generates a complete valid object from one definition, UserFactory(role="admin") overrides only the field being tested, and UserFactory.create_batch(100) generates load-test data in one line with unique emails from Sequence. For the model_bakery (Django alternative) — model_bakery auto-fills all required fields using defaults without any factory definition, but field values are random and not reproducible, while factory_boy’s Sequence(lambda n: f"user{n}") and Faker.seed(42) produce deterministic data across test runs, SubFactory builds the full object graph in one call, and Trait lets a single factory express admin, inactive, and premium variants without separate classes. The Claude Skills 360 bundle includes factory_boy skill sets covering Factory and DjangoModelFactory, Sequence for unique fields, LazyAttribute for derived values, SubFactory for related objects, Trait for behavior variants, post_generation for password hashing, create_batch and build_batch, Iterator and FuzzyChoice, pytest fixture integration, and SQLAlchemy session-scoped factories. Start with the free tier to try test factory code generation.