Shared test data: fixtures
On this page
When several test files need the same data, write a fixture. A fixture is
a test-only action, in the domain's fixtures/ folder, that creates data
by calling the application's own actions and returns what the tests need:
// domains/planning/fixtures/alice_with_todos.jadpo/// Alice, signed up, with two open todos.fixture alice_with_todos() -> Principal.user { var user: User = attempt register_user("alice@example.test") var alice: Principal.user = Principal.user(user) attempt create_todo(CreateTodo { title: "Buy milk" }, alice) attempt create_todo(CreateTodo { title: "Book dentist" }, alice) return alice}
A test calls a fixture by name, like any action. Because the fixture goes
through the real actions, validation, lifecycle and policy apply exactly
as they do for a user. Seeds build their data the same way
(Seeds). A failure inside a fixture fails the test
that called it, so a fixture has no fails list.
Who can call a fixture
Only tests, other fixtures and seeds can call a fixture. An action that
calls one is semantic.not_callable.
A fixture for other domains: public
A fixture is private to its domain unless it is marked public. Another
domain's tests, fixtures and seeds, and a scenario in root tests/, call
a public fixture through its domain, like an action:
planning.alice_with_todos(). Calling a private one from outside its
domain is visibility.private_cross_domain. public offers a fixture to
tests, fixtures and seeds only: application code still never calls it.
Here planning offers a public fixture, and both a reminders test and a
scenario call it:
// domains/planning/fixtures/two_open_todos.jadpouse planning/types { TodoCount }use planning/actions { add_todo }/// Two open todos, added the way a user adds them.public fixture two_open_todos() -> TodoCount { var one: TodoCount = add_todo(0) return add_todo(one)}
// domains/reminders/actions/send_reminders.test.jadpouse planningtest "reminders start from planning's open todos" { send_reminders() assert planning.two_open_todos() == 2}
// tests/a_plan_starts_with_two_todos.test.jadpouse planningtest "a plan starts with two open todos" { assert planning.two_open_todos() == 2}
Seeds and fixtures
A seed is data for local development and demos, made the same way:
| Seed | Fixture | |
|---|---|---|
| Lives in | database/seeds/ |
a domain's fixtures/ |
| Used by | jadpo seed <name>, for local development and demos |
tests |
| Creates data by | calling actions | calling actions |
| Data lasts | until you reset the local database | until the test ends |
| In production | refused | never compiled in |
A seed may call fixtures, so demo data and test data share their builders. A test never loads a seed: seeds change whenever the demo changes.
Data builders
A data builder makes one record with valid defaults, for a test that
needs a record in a particular state. The compiler generates one for each
entity: make.Todo { title: "Buy milk" } fills every other required field
with a valid default and goes through the real create operation, so
validation and policy apply. A builder may set any field that isn't
generated, including ones ordinary inputs don't accept, so hard-to-reach
states are easy to build.
With builders, fixtures remain for meaningful scenarios. They return a
named object holding what they made (setup.alice, setup.todos), so a
test never has to look its own data up again.
Data builders, and the named object a fixture returns with them, aren't built yet. Until they are, build the data with a fixture that returns what the tests need, as above.
Markdown for agents: /docs/shared-test-data-fixtures.md.Built from Jadpo main at a707efb.