Factories¶
Factories generate test data with sensible defaults. bluefox-test uses factory_boy for attribute declaration and handles all database persistence with native async calls.
BaseFactory¶
Extends factory.Factory (not SQLAlchemyModelFactory). Subclass it, declare fields, call register().
from bluefox_test import BaseFactory, Faker, register
from users.models import User
class UserFactory(BaseFactory):
class Meta:
model = User
name = Faker("name")
email = Faker("email")
is_active = True
create_user = register(UserFactory)
How it works¶
factory_boybuilds a Python object with fake data (no DB)create_async()adds it to theAsyncSessionand callsawait session.flush()await session.refresh(instance)loads server-generated defaults (timestamps, IDs)
No greenlet bridge, no sync hacks. Pure async.
create_async()¶
| Parameter | Description |
|---|---|
_session | Explicit session override for cross-bind factories. If None, uses the globally-bound test session. |
**overrides | Field overrides passed to factory_boy's .build(). |
Returns the persisted model instance with a real database ID.
create_batch_async()¶
Creates multiple instances. Each gets unique fake data.
register()¶
Turns a factory into a global pytest fixture. The fixture name is derived from the model class name:
| Model name | Fixture name |
|---|---|
User | create_user |
UserProfile | create_user_profile |
HTTPLog | create_http_log |
APIKey | create_api_key |
The returned fixture yields an async callable:
Cross-bind factories¶
For models in a non-default database, specify the session fixture:
Available re-exports¶
These are re-exported from factory_boy for convenience:
from bluefox_test import (
BaseFactory, # base class for factories
register, # turn a factory into a pytest fixture
Faker, # fake data generation
LazyFunction, # computed field (called once per build)
LazyAttribute, # computed field (has access to other fields)
Sequence, # sequential values (0, 1, 2, ...)
Iterator, # cycle through values
Trait, # named sets of field overrides
)
Tip
SubFactory is intentionally not re-exported. Use explicit relationship passing instead.
Relationship handling¶
Don't use SubFactory for required relationships. Pass related objects explicitly in your tests:
from bluefox_test import BaseFactory, Faker, register
from orders.models import Order
class OrderFactory(BaseFactory):
class Meta:
model = Order
status = "pending"
# owner is NOT declared — always pass explicitly
create_order = register(OrderFactory)
async def test_create_order(create_user, create_order):
user = await create_user(name="Hugo")
order = await create_order(owner=user, status="pending")
assert order.owner_id == user.id
For optional relationships, use None as the default:
class OrderFactory(BaseFactory):
class Meta:
model = Order
status = "pending"
coupon = None # optional relationship
Factory file pattern¶
from bluefox_test import BaseFactory, Faker, register
from users.models import User, UserProfile
class UserFactory(BaseFactory):
class Meta:
model = User
name = Faker("name")
email = Faker("email")
is_active = True
create_user = register(UserFactory)
class UserProfileFactory(BaseFactory):
class Meta:
model = UserProfile
bio = Faker("paragraph")
avatar_url = "https://example.com/avatar.png"
create_user_profile = register(UserProfileFactory)
Auto-discovery¶
bluefox_test_setup() calls discover_and_collect_fixtures() which walks src_root for tests/factories.py files, imports them (triggering register() calls), and merges the fixtures into the returned dict.
Requirements:
- Factory files must be named
tests/factories.py - Every
tests/directory needs an__init__.py pythonpath = ["src"]inpyproject.toml