Skip to content

Cookbook

Common patterns and recipes for using factorio effectively in your tests.

Generating Unique Values

Sequential IDs

Use a counter to generate unique sequential values:

from factorio import fields
from factorio.factories import Factory

_counter = 0

def next_id():
    global _counter
    _counter += 1
    return _counter

class UserFactory(Factory[User]):
    user_id = fields.ConstantField(None)  # Will be overridden

    @classmethod
    def build(cls, **kwargs):
        if 'user_id' not in kwargs:
            kwargs['user_id'] = next_id()
        return super().build(**kwargs)

# Usage
user1 = UserFactory.build()  # user_id = 1
user2 = UserFactory.build()  # user_id = 2
user3 = UserFactory.build()  # user_id = 3

UUID Generation

import uuid
from factorio import fields

class EntityFactory(Factory[Entity]):
    id = fields.ConstantField(str(uuid.uuid4()))

# Each entity gets a unique UUID
entity1 = EntityFactory.build()
entity2 = EntityFactory.build()
assert entity1.id != entity2.id

Unique Emails

_email_counter = 0

def unique_email():
    global _email_counter
    _email_counter += 1
    return f"user{_email_counter}@example.com"

class UserFactory(Factory[User]):
    email = fields.ConstantField(None)

    @classmethod
    def build(cls, **kwargs):
        if 'email' not in kwargs:
            kwargs['email'] = unique_email()
        return super().build(**kwargs)

# All emails are unique
users = [UserFactory.build() for _ in range(100)]
emails = [u.email for u in users]
assert len(emails) == len(set(emails))  # No duplicates

Conditional Field Generation

Based on Other Fields

Generate values conditionally based on other field values:

from factorio import fields
from factorio.factories import Factory

class UserFactory(Factory[User]):
    age = fields.IntegerField(min_value=0, max_value=100)
    is_adult = fields.ConstantField(None)

    @classmethod
    def build(cls, **kwargs):
        instance = super().build(**kwargs)
        # Set is_adult based on age
        instance.is_adult = instance.age >= 18
        return instance

user = UserFactory.build(age=25)
assert user.is_adult is True

user = UserFactory.build(age=15)
assert user.is_adult is False

Random Selection from Categories

class ProductFactory(Factory[Product]):
    category = fields.ChoiceField(["electronics", "books", "clothing"])
    subcategory = fields.ConstantField(None)

    @classmethod
    def build(cls, **kwargs):
        instance = super().build(**kwargs)

        # Set subcategory based on category
        if instance.category == "electronics":
            instance.subcategory = "phones"
        elif instance.category == "books":
            instance.subcategory = "fiction"
        else:
            instance.subcategory = "shirts"

        return instance

Lazy Evaluation

Deferred Computation

Use lambda or callable for lazy evaluation:

from datetime import datetime

class EventFactory(Factory[Event]):
    created_at = fields.DateTimeField()
    updated_at = fields.ConstantField(None)

    @classmethod
    def build(cls, **kwargs):
        instance = super().build(**kwargs)
        # Set updated_at to current time (lazy)
        if instance.updated_at is None:
            instance.updated_at = datetime.utcnow()
        return instance

Dynamic Ranges

class SalaryFactory(Factory[Salary]):
    base_salary = fields.IntegerField(min_value=30000, max_value=50000)
    bonus_percentage = fields.IntegerField(min_value=5, max_value=20)
    total_compensation = fields.ConstantField(None)

    @classmethod
    def build(cls, **kwargs):
        instance = super().build(**kwargs)
        # Calculate based on generated values
        instance.total_compensation = int(
            instance.base_salary * (1 + instance.bonus_percentage / 100)
        )
        return instance

Testing with Realistic Data

Realistic Names and Addresses

class CustomerFactory(Factory[Customer]):
    first_name = fields.TextField("first_name")
    last_name = fields.TextField("last_name")
    full_name = fields.ConstantField(None)
    email = fields.TextField("safe_email")
    phone = fields.TextField("phone_number")
    address = fields.TextField("address")
    city = fields.TextField("city")
    country = fields.TextField("country")
    postcode = fields.TextField("postcode")

    @classmethod
    def build(cls, **kwargs):
        instance = super().build(**kwargs)
        instance.full_name = f"{instance.first_name} {instance.last_name}"
        return instance

customer = CustomerFactory.build()
assert customer.full_name  # "John Smith"
assert "@" in customer.email  # "john.smith@example.com"
assert customer.phone  # "+1-555-123-4567"

Realistic Financial Data

from decimal import Decimal

class TransactionFactory(Factory[Transaction]):
    amount = fields.DecimalField(
        min_value=Decimal("0.01"),
        max_value=Decimal("9999.99"),
        accuracy=2
    )
    currency = fields.ChoiceField(["USD", "EUR", "GBP", "JPY"])
    description = fields.TextField("sentence", nb_words=8)
    timestamp = fields.DateTimeField()

transaction = TransactionFactory.build()
assert isinstance(transaction.amount, Decimal)
assert transaction.currency in ["USD", "EUR", "GBP", "JPY"]

Realistic Internet Data

class WebsiteFactory(Factory[Website]):
    domain = fields.TextField("domain_name")
    url = fields.TextField("url")
    ip_address = fields.TextField("ipv4")
    user_agent = fields.TextField("user_agent")
    email = fields.TextField("company_email")

website = WebsiteFactory.build()
assert website.url.startswith("http")
assert "." in website.ip_address  # Valid IP format

Performance Considerations

Bulk Generation

For large datasets, generate in batches:

def test_large_dataset():
    # Generate 1000 users efficiently
    users = [UserFactory.build() for _ in range(1000)]

    assert len(users) == 1000
    assert all(isinstance(u, User) for u in users)

Tip: List comprehensions are faster than loops for bulk generation.

Avoid Unnecessary Nested Factories

# ❌ Slow - creates entire object graph
class SlowOrderFactory(Factory[Order]):
    customer = fields.FactoryField(CustomerFactory)
    items = fields.ListField(fields.FactoryField(ItemFactory), length=10)
    shipping_address = fields.FactoryField(AddressFactory)
    billing_address = fields.FactoryField(AddressFactory)

# ✅ Faster - only generate what you need
class FastOrderFactory(Factory[Order]):
    customer_id = fields.IntegerField(min_value=1, max_value=1000)
    item_count = fields.IntegerField(min_value=1, max_value=10)
    total_amount = fields.DecimalField(min_value=1, max_value=999)

def test_performance():
    import time

    start = time.time()
    orders = [FastOrderFactory.build() for _ in range(1000)]
    elapsed = time.time() - start

    print(f"Generated 1000 orders in {elapsed:.2f}s")

Cache Reusable Factories

# Create factory once, reuse many times
user_factory = UserFactory

def test_multiple_scenarios():
    # Reuse same factory
    user1 = user_factory.build()
    user2 = user_factory.build()
    user3 = user_factory.build()

Debugging Factory Issues

Inspect Generated Values

def debug_factory():
    user = UserFactory.build()

    # Print all fields
    print(f"Name: {user.name}")
    print(f"Email: {user.email}")
    print(f"Age: {user.age}")

    # Or convert to dict if supported
    if hasattr(user, '__dict__'):
        print(user.__dict__)

Test Field Ranges

def test_field_ranges():
    # Generate many instances to verify distribution
    ages = [UserFactory.build().age for _ in range(100)]

    assert min(ages) >= 18
    assert max(ages) <= 65
    assert len(set(ages)) > 10  # Good variety

    print(f"Age range: {min(ages)}-{max(ages)}")
    print(f"Unique values: {len(set(ages))}")

Verify Type Correctness

def test_types():
    user = UserFactory.build()

    assert isinstance(user.name, str), f"Expected str, got {type(user.name)}"
    assert isinstance(user.age, int), f"Expected int, got {type(user.age)}"
    assert isinstance(user.email, str), f"Expected str, got {type(user.email)}"

Common Patterns

Timestamped Models

from datetime import datetime

class TimestampedFactory(Factory[T]):
    created_at = fields.DateTimeField()
    updated_at = fields.DateTimeField()

    @classmethod
    def build(cls, **kwargs):
        # Ensure updated_at >= created_at
        instance = super().build(**kwargs)
        if instance.updated_at < instance.created_at:
            instance.updated_at = instance.created_at
        return instance

class ArticleFactory(TimestampedFactory):
    title = fields.TextField("sentence")
    content = fields.TextField("paragraphs", nb=3)

Soft Delete Pattern

class SoftDeletableFactory(Factory[T]):
    is_deleted = fields.BooleanField(truth_probability=10)  # 10% deleted
    deleted_at = fields.ConstantField(None)

    @classmethod
    def build(cls, **kwargs):
        instance = super().build(**kwargs)
        if instance.is_deleted and instance.deleted_at is None:
            instance.deleted_at = datetime.utcnow()
        return instance

class PostFactory(SoftDeletableFactory):
    title = fields.TextField("sentence")
    content = fields.TextField("paragraph")

Versioned Models

class VersionedFactory(Factory[T]):
    version = fields.IntegerField(min_value=1, max_value=10)
    is_latest = fields.ConstantField(None)

    @classmethod
    def build(cls, **kwargs):
        instance = super().build(**kwargs)
        instance.is_latest = instance.version == 10
        return instance

Enum-Like Fields

class OrderFactory(Factory[Order]):
    status = fields.ChoiceField([
        "pending",
        "processing",
        "shipped",
        "delivered",
        "cancelled"
    ])
    priority = fields.ChoiceField(["low", "medium", "high", "critical"])

order = OrderFactory.build()
assert order.status in ["pending", "processing", "shipped", "delivered", "cancelled"]

Integration with pytest

pytest Fixtures with Factories

import pytest

@pytest.fixture
def user():
    return UserFactory.build()

@pytest.fixture
def admin_user():
    return UserFactory.build(
        is_admin=True,
        role="administrator"
    )

@pytest.fixture
def multiple_users():
    return [UserFactory.build() for _ in range(5)]

def test_with_fixture(user):
    assert user.name
    assert user.email

def test_admin(admin_user):
    assert admin_user.is_admin is True

def test_bulk(multiple_users):
    assert len(multiple_users) == 5

Parametrized Tests

import pytest

@pytest.mark.parametrize("age_range", [
    (18, 25),
    (26, 40),
    (41, 65),
])
def test_age_groups(age_range):
    min_age, max_age = age_range
    user = UserFactory.build(
        age=fields.IntegerField(min_value=min_age, max_value=max_age)
    )

    assert min_age <= user.age <= max_age

Factory as pytest Fixture

@pytest.fixture
def user_factory():
    return UserFactory

def test_custom_user(user_factory):
    user = user_factory.build(name="Custom Name")
    assert user.name == "Custom Name"

Best Practices Summary

DO:

✅ Use realistic data with TextField ✅ Override fields for specific test scenarios ✅ Keep factories simple and focused ✅ Use collection variation for realistic diversity ✅ Document non-obvious field choices ✅ Test edge cases with explicit overrides

DON'T:

❌ Hardcode values in factories (use fields instead) ❌ Create overly complex factory hierarchies ❌ Ignore type hints ❌ Generate unnecessarily large object graphs ❌ Forget that .build() is required ❌ Assume database persistence happens automatically

Quick Reference

# Basic factory
class MyFactory(Factory[MyModel]):
    field1 = fields.TextField("word")
    field2 = fields.IntegerField(min_value=1, max_value=100)
    field3 = fields.BooleanField(truth_probability=80)

# Build instance
instance = MyFactory.build()

# Override fields
instance = MyFactory.build(field1="custom", field2=42)

# Nested factories
class ParentFactory(Factory[Parent]):
    child = fields.FactoryField(ChildFactory)

# Collections
items = fields.ListField(fields.StringField(), length=5, variation=2)