Skip to content

Factories

Tests need data — lots of it, cheaply, and with just enough realism to exercise constraints. ModelFactory generates ArvelModel instances for unit tests (make) and persists them through an async session (create, create_many).

Factories are plain subclasses: you declare __model__, override defaults(), and optionally define named states for common variations.

A minimal factory

from typing import Any, ClassVar

from arvel.testing.factory import ModelFactory


class UserFactory(ModelFactory[User]):
    __model__: ClassVar[type[User]] = User

    @classmethod
    def defaults(cls) -> dict[str, Any]:
        seq = cls._next_seq()
        return {
            "name": f"User {seq}",
            "email": f"user{seq}@test.example",
        }

_next_seq() gives a per-class counter so emails stay unique across examples.

Making instances

user = UserFactory.make()
user = await UserFactory.create(session=session)
users = await UserFactory.create_many(5, session=session)

make never touches the database — perfect for fast pure tests. create flushes and refreshes so autoincrement IDs and server defaults are visible.

States

Define state_<name>() class methods returning override dicts, then chain UserFactory.state("admin"):

@classmethod
def state_admin(cls) -> dict[str, Any]:
    return {"role": "admin", "name": "Admin User"}
admin = await UserFactory.state("admin").create(session=session)

FactoryBuilder is the object returned by state() — it supports make, create, create_many, and make_batch with the state merged in.

Batches

make_batch and batch / create_many generate multiple rows with the same overrides. Override per-call kwargs when only a few fields differ.

Resetting sequences

Call _reset_seq() in test teardown if a class-level counter would otherwise leak across examples — rare, but available for strict isolation.

Factories trade a little upfront code for immense downstream speed: readable tests, fewer fixtures, and confidence that your schema rules actually hold.