Skip to content

Add fixture inheritance with extends support#30

Merged
ngan merged 1 commit intomainfrom
np-fixture-inheritance
Feb 26, 2026
Merged

Add fixture inheritance with extends support#30
ngan merged 1 commit intomainfrom
np-fixture-inheritance

Conversation

@ngan
Copy link
Collaborator

@ngan ngan commented Feb 26, 2026

Summary

Adds fixture inheritance via extends:, enabling parent-child relationships that share setup data without duplication. Child fixtures can access parent exposed records through a parent helper and their cache files are self-contained (include all parent model records for replay).

Inheritance API

Named fixtures (spec/fixture_kit/child.rb):

FixtureKit.define(extends: "base") do
  project = Project.create!(name: "Child Project", owner: parent.owner)
  expose(project: project)
end

Inline fixtures (RSpec/Minitest):

fixture(extends: "base") do
  project = Project.create!(name: "Inline Project", owner: parent.owner)
  expose(project: project)
end

Multi-level chains work automatically — grandchild extends child extends base. Parent fixtures are generated before children. Only the child's explicitly exposed records are accessible; parent records are not auto-exposed.

Circular dependency detection

The Registry tracks a @resolving stack during fixture construction. If a name is encountered that's already on the stack, CircularFixtureInheritance is raised immediately at registration time (fail fast, no deferred errors).

Internal refactors

  • Fixture#cacheFixture#generate (the verb for triggering cache generation); Fixture#cache is now the attr_reader for the Cache object
  • Registry#add(name_or_definition, scope) — name-first argument order
  • Runner#register simplified to a passthrough to Registry
  • RSpec/Minitest entrypoints now construct Definition objects themselves before calling register
  • Cache uses MemoryData = Data.define(:records, :exposed) with model classes as hash keys instead of string names
  • Repository#load_record uses { ModelClass => id } format with .keys.first / .values.first

Test plan

  • All 112 unit + integration examples pass (bundle exec rspec)
  • RSpec integration tests pass (FIXTURE_KIT_INTEGRATION_FRAMEWORK=rspec)
  • Minitest integration tests pass (FIXTURE_KIT_INTEGRATION_FRAMEWORK=minitest)
  • Both frameworks have identical assertion coverage (19 FKIT_ASSERT markers each)
  • Inheritance chain: grandchild → child → base with record counts and association traversal
  • Inline inheritance: anonymous fixture with extends: and parent helper
  • Circular detection: CircularFixtureInheritance raised at registration time
  • Parent records present in DB but not auto-exposed (NoMethodError verified)

🤖 Ngan's AI agent

Fixtures can now extend other fixtures via `extends:`, enabling
parent-child relationships that share setup data without duplication.

Key changes:

**Inheritance via `extends:`**
- Named fixtures: `FixtureKit.define(extends: "parent") { ... }`
- Inline fixtures: `fixture(extends: "parent") { ... }`
- Multi-level chains: grandchild -> child -> base
- `parent` helper available in definition blocks to access
  parent's exposed records

**Cache includes parent records**
- Child cache files include all parent model records so replay
  is self-contained
- Parent fixtures are generated before children automatically

**Circular dependency detection**
- Registry tracks a `@resolving` stack during fixture construction
- Re-entering a name already on the stack raises
  `CircularFixtureInheritance` at registration time (fail fast)

**Internal refactors**
- `Fixture#cache` renamed to `Fixture#generate` (verb for triggering
  generation), `@cache` is now the Cache object reader
- `Registry#add(name_or_definition, scope)` — name-first signature
- `Runner#register` is a simple passthrough to registry
- RSpec/Minitest entrypoints create `Definition` objects themselves
- Cache uses `MemoryData = Data.define(:records, :exposed)` with
  model classes as keys instead of string names
- `Repository#load_record` uses `{ ModelClass => id }` format

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ngan ngan merged commit 0c0dc19 into main Feb 26, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant