Skip to content

Declaring Fixtures

Ngan Pham edited this page Feb 26, 2026 · 3 revisions

Declaring Fixtures

Fixture declarations can be named (file-backed), anonymous (inline block), or inherited (extending another fixture).

Named Fixtures

Reference a fixture file by name. FixtureKit loads the corresponding .rb file from your configured fixture_path:

RSpec.describe Project do
  fixture "project_management"

  it "loads the fixture data" do
    expect(fixture.project).to be_present
  end
end

Anonymous Fixtures

Define fixture data inline when you don't need a reusable file:

RSpec.describe User do
  fixture do
    owner = User.create!(name: "Alice")
    project = Project.create!(name: "Roadmap", owner: owner)
    expose(owner: owner, project: project)
  end

  it "exposes the created records" do
    expect(fixture.owner.name).to eq("Alice")
    expect(fixture.project.owner).to eq(fixture.owner)
  end
end

Anonymous fixtures are cached using a context-derived identifier path. See Anonymous Fixtures and Identifiers.

Fixture Inheritance

Fixtures can extend other fixtures with extends:. The child fixture gets access to the parent's exposed records through a parent helper, and its cache includes all parent data so replay is self-contained.

Named fixture inheritance

Define a base fixture (spec/fixture_kit/company/base.rb):

FixtureKit.define do
  company = Company.create!(name: "Acme Corp")
  owner = User.create!(name: "Alice", company: company, role: "owner")

  expose(company: company, owner: owner)
end

Then extend it (spec/fixture_kit/company/with_employees.rb):

FixtureKit.define(extends: "company/base") do
  employee = User.create!(name: "Bob", company: parent.company, role: "employee")
  payroll = Payroll.create!(company: parent.company)

  expose(employee: employee, payroll: payroll)
end

Use it in a spec:

RSpec.describe Payroll do
  fixture "company/with_employees"

  it "belongs to the company" do
    expect(fixture.payroll.company).to eq(fixture.employee.company)
  end
end

Inline fixture inheritance

Extend a named fixture directly in a test without creating a separate file:

RSpec.describe "onboarding flow" do
  fixture(extends: "company/base") do
    onboarding = Onboarding.create!(company: parent.company, admin: parent.owner)
    expose(onboarding: onboarding)
  end

  it "creates an onboarding for the company owner" do
    expect(fixture.onboarding.admin).to eq(fixture.onboarding.company.users.find_by(role: "owner"))
  end
end

Multi-level chains

Inheritance chains work to any depth. Each level extends the previous:

# spec/fixture_kit/company/with_payroll.rb
FixtureKit.define(extends: "company/with_employees") do
  pay_period = PayPeriod.create!(payroll: parent.payroll, start_date: Date.current)
  expose(pay_period: pay_period)
end

Parent fixtures are generated before their children automatically.

What gets exposed

Only the child's explicitly exposed records are accessible via fixture.*. Parent records are not auto-exposed — you must re-expose them if you need direct access:

FixtureKit.define(extends: "company/base") do
  employee = User.create!(name: "Bob", company: parent.company, role: "employee")
  # fixture.company won't work unless you re-expose it:
  expose(employee: employee, company: parent.company)
end

However, parent records are present in the database and can be reached through associations (e.g., fixture.employee.company).

Circular inheritance

Circular chains (A extends B, B extends A) are detected at registration time and raise FixtureKit::CircularFixtureInheritance.

Nested Behavior

  • Child contexts can override parent fixture declarations by declaring their own fixture.
  • Override is framework-native:
  • RSpec: nested example groups
  • Minitest: subclasses of test classes

expose

Within FixtureKit.define (or an anonymous declaration), use expose to define what tests can read:

FixtureKit.define do
  account = Account.create!(name: "Acme")
  users = User.where(account: account).to_a

  expose(account: account, users: users)
end

Rules:

  • Exposed names become repository reader methods.
  • Duplicate exposed names raise FixtureKit::DuplicateNameError.

Declaration Constraints

  • Exactly one of name or block is required.
  • Providing both name and block silently uses the name (the block is ignored).
  • Declaring more than one fixture in the same context/class raises FixtureKit::MultipleFixtures.

Clone this wiki locally