Collection of helpers for dealing with fixtures in RSpec
Read the post about the library on dev.to.
gem "fixturama"
On Rails add offsets to id sequences of database tables.
# spec/rails_helper.rb
RSpec.configure do |config|
config.before(:suite) { Fixturama.start_ids_from 1_000_000 }
end
Now when you hardcode ids in fixtures (under 1_000_000), they won't conflict with authomatically created ones.
# spec/spec_helper.rb
require "fixturama/rspec"
The gem defines 3 helpers (support ERB bindings):
load_fixture(path, **opts)
to load data from a fixture, and deserialize YAML and JSONseed_fixture(path_to_yaml, **opts)
to prepare database using the FactoryBotstub_fixture(path_to_yaml, **opts)
to stub some classes
# spec/models/user/_spec.rb
RSpec.describe "GraphQL mutation 'deleteProfile'" do
subject { Schema.execute(mutation).to_h }
before do
seed_fixture("#{__dir__}/database.yml", profile_id: 42)
stub_fixture("#{__dir__}/stubs.yml", profile_id: 42)
end
let(:mutation) { load_fixture "#{__dir__}/mutation.graphql", profile_id: 42 }
let(:result) { load_fixture "#{__dir__}/result.yaml" }
it { is_expected.to eq result }
it "deletes the profile" do
expect { subject }.to change { Profile.find_by(id: 42) }.to nil
end
it "sends a notification" do
expect(Notifier)
.to receive_message_chain(:create)
.with("profileDeleted", 42)
subject
end
end
Notice, that since the v0.0.6
the gem also supports binding any ruby object, not only strings, booleans and numbers:
# ./data.yml
---
account: <%= user %>
# Bind activerecord model
subject { load_fixture "#{__dir__}/data.yml", user: user }
let(:user) { FactoryBot.create :user }
# The same object will be returned
it { is_expected.to eq account: user }
The object must be named in the options you send to the load_fixture
, stub_fixture
, or seed_fixture
helpers.
This feature can also be useful to produce a "partially defined" fixtures with RSpec argument matchers:
subject { load_fixture "#{__dir__}/data.yml", user: kind_of(ActiveRecord::Base) }
The seed (seed_fixture
) file should be a YAML/JSON with opinionated parameters, namely:
type
for the name of the FactoryBot factorytraits
for the factory traitsparams
for parameters of the factory
# ./database.yml
#
# This is the same as
# `create_list :profile, 1, :active, id: profile_id`
---
- type: profile
traits:
- active
params:
id: <%= profile_id %>
Use the count: 2
key to create more objects at once.
Another opinionated format we use for stubs (stub_fixture
). The gem supports stubbing both message chains and constants.
For message chains:
class
for stubbed classchain
for messages chainarguments
(optional) for specific argumentsactions
for an array of actions for consecutive invocations of the chain
For constants:
const
for stubbed constantvalue
for a value of the constant
Every action either return
some value, or raise
some exception
# ./stubs.yml
#
# The first invocation acts like
#
# allow(Notifier)
# .to receive_message_chain(:create)
# .with(:profileDeleted, 42)
# .and_return true
#
# then it will act like
#
# allow(Notifier)
# .to receive_message_chain(:create)
# .with(:profileDeleted, 42)
# .and_raise ActiveRecord::RecordNotFound
#
---
- class: Notifier
chain:
- create
arguments:
- :profileDeleted
- <%= profile_id %>
actions:
- return: true
- raise: ActiveRecord::RecordNotFound
- const: NOTIFIER_TIMEOUT_SEC
value: 10
mutation {
deleteProfile(
input: {
id: "<%= profile_id %>"
}
) {
success
errors {
message
fields
}
}
}
# ./result.yaml
---
data:
deleteProfile:
success: true
errors: []
With these helpers all the concrete settings can be extracted to fixtures.
I find it especially helpful when I need to check different edge cases. Instead of polluting a specification with various parameters, I create the sub-folder with "input" and "output" fixtures for every case.
Looking at the spec I can easily figure out the "structure" of expectation, while looking at fixtures I can check the concrete corner cases.
The gem is available as open source under the terms of the MIT License.