Canonical API, configuration, and contract reference.
- Definition:
FixtureKit.define { ... }returns aFixtureKit::Definition. - Fixture: wraps an identifier and a definition; handles cache save/mount.
- Cache: persists and replays SQL for touched models.
- Repository: exposes records via methods, loaded lazily and memoized per test.
- Runner: owns configuration, registry, startup state, and adapter instance.
Require:
require "fixture_kit/rspec"Effects:
- Configures
fixture_pathdefault to"spec/fixture_kit". - Configures adapter to
FixtureKit::RSpecAdapter. - Adds
fixtureclass macro for example groups. - Adds
fixtureinstance reader for examples.
Require:
require "fixture_kit/minitest"Effects:
- Configures
fixture_pathdefault to"test/fixture_kit". - Configures adapter to
FixtureKit::MinitestAdapter. - Adds
fixtureclass macro for test classes. - Adds
fixtureinstance reader for tests.
Declaration signature in both frameworks:
fixture(name = nil, extends: nil, &definition_block)Rules:
- Provide exactly one of
nameor block. - Both provided: raises
FixtureKit::InvalidFixtureDeclaration. - Neither provided: raises
FixtureKit::InvalidFixtureDeclaration. extendscan be combined with a block for inline inheritance (see Fixture Inheritance).- More than one declaration in same context/class: raises
FixtureKit::MultipleFixtures. - Nested context/class can declare its own fixture and override parent declaration.
Named fixture lookup:
- Reads
<fixture_path>/<name>.rb. - File must evaluate to
FixtureKit::Definition. - Missing file or invalid return value raises
FixtureKit::FixtureDefinitionNotFound.
Anonymous fixture:
- Declared inline via block.
- Identifier is derived from framework scope class and normalized by adapter.
FixtureKit.define do
# setup data
expose(user: user)
endDefinition#expose(**records):
- Exposed names become repository methods.
- Duplicate exposed names raise
FixtureKit::DuplicateNameError.
FixtureKit.define(extends: "base_fixture_name") do
# parent records are available via `parent`
record = SomeModel.create!(related: parent.exposed_name)
expose(record: record)
endextends accepts a named fixture string. The parent fixture is generated and mounted before the child definition runs.
Inside the definition block, parent returns the parent fixture's Repository. Use it to reference the parent's exposed records:
FixtureKit.define(extends: "project_management") do
task = Task.create!(project: parent.project, assignee: parent.owner)
expose(task: task)
endInheritance can be chained — a child can extend a fixture that itself extends another:
# base.rb
FixtureKit.define do
owner = User.create!(name: "Owner", email: "owner@example.com")
expose(owner: owner)
end
# child.rb
FixtureKit.define(extends: "base") do
project = Project.create!(name: "Project", owner: parent.owner)
expose(project: project)
end
# grandchild.rb
FixtureKit.define(extends: "child") do
task = Task.create!(title: "Task", project: parent.project, assignee: parent.project.owner)
expose(task: task)
endextends works with both named and anonymous (inline) fixtures:
fixture(extends: "project_management") do
task = Task.create!(title: "Inline Task", project: parent.project, assignee: parent.owner)
expose(task: task)
endParent records are not auto-exposed in the child. Only names explicitly passed to expose in the child definition are available on the test fixture reader. The parent's database records are still inserted — they just aren't accessible by name unless re-exposed.
Circular extends chains are detected at registration time and raise FixtureKit::CircularFixtureInheritance.
Configure via:
FixtureKit.configure do |config|
# ...
endDefault values:
fixture_path:"fixture_kit"cache_path:"tmp/cache/fixture_kit"- adapter class:
FixtureKit::MinitestAdapter - adapter options:
{}
config.fixture_path = String
- Base directory for named fixture files.
config.cache_path = String
- Base directory for cache JSON files.
config.adapter(adapter_class = nil, **options)
- Getter: no args returns current adapter class.
- Setter: stores adapter class and options for adapter initialization.
config.callbacks
- Returns callback registry (
FixtureKit::Callbacks).
Subclass FixtureKit::Adapter and implement:
#execute { ... }
- Runs fixture generation in framework-specific isolation.
#identifier_for(identifier)
- Receives non-string fixture identifier and returns normalized String identifier.
_anonymous/prefixing is applied byFixtureKit::Cache.
Adapter initialization:
adapter_instance = config.adapter.new(config.adapter_options)Options are available via attr_reader :options in FixtureKit::Adapter.
Register using configuration methods:
config.on_cache_save { |identifier| ... }
- Runs before cache save.
config.on_cache_saved { |identifier, duration| ... }
- Runs after cache save.
durationis elapsed seconds asFloat.
config.on_cache_mount { |identifier| ... }
- Runs before cache mount.
config.on_cache_mounted { |identifier, duration| ... }
- Runs after cache mount.
durationis elapsed seconds asFloat.
Behavior:
- Multiple callbacks per event supported.
- Callbacks run in registration order.
identifieris always a String cache identifier (no.jsonsuffix).
Cache file path format:
<cache_path>/<identifier>.json
Identifier behavior:
- Named fixture: identifier is the fixture name string.
- Anonymous fixture: identifier is
_anonymous/<adapter-normalized-scope>.
Examples:
- Named:
teams/basic->tmp/cache/fixture_kit/teams/basic.json - Anonymous RSpec:
_anonymous/foo/with_fixture_kit/hello - Anonymous Minitest:
_anonymous/my_feature_test
fixture
- Returns
FixtureKit::Repositoryfor the mounted fixture.
Repository
- Methods are generated from exposed names.
- First access loads model with
find_by(id: ...). - Loaded value is memoized for subsequent access in that test.
- If row is missing before first access, value is
nil.
FIXTURE_KIT_PRESERVE_CACHE
- If truthy, runner start does not clear cache directory.
- Truthy values (case-insensitive):
1,true,yes.
Public error classes:
FixtureKit::ErrorFixtureKit::DuplicateNameErrorFixtureKit::InvalidFixtureDeclarationFixtureKit::MultipleFixturesFixtureKit::CacheMissingErrorFixtureKit::FixtureDefinitionNotFoundFixtureKit::RunnerAlreadyStartedErrorFixtureKit::CircularFixtureInheritance
- Ruby >= 3.3
- ActiveRecord >= 8.0
- ActiveSupport >= 8.0