Skip to content
Collection of helpers for dealing with fixtures in RSpec
Branch: master
Clone or download
Latest commit f757a85 Jun 9, 2019
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
lib Support serialization of Ruby objects via ERB Jun 9, 2019
spec Support serialization of Ruby objects via ERB Jun 9, 2019
.gitignore Add RubyMine .idea/ to ignored paths Jun 9, 2019
.rspec Initial commit Jan 30, 2019
.rubocop.yml Bump v0.0.2 Apr 27, 2019
.travis.yml Bump v0.0.2 Apr 27, 2019
CHANGELOG.md Bump v0.0.6 Jun 9, 2019
Gemfile Initial commit Jan 30, 2019
LICENSE.txt Initial commit Jan 30, 2019
README.md Update README.md Jun 9, 2019
Rakefile Initial commit Jan 30, 2019
fixturama.gemspec Bump v0.0.6 Jun 9, 2019

README.md

Fixturama

Collection of helpers for dealing with fixtures in RSpec

Read the post about the library on dev.to.

Sponsored by Evil Martians

Gem Version Build Status

Installation

gem "fixturama"

Configuration

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.

Usage

# 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 JSON
  • seed_fixture(path_to_yaml, **opts) to prepare database using the FactoryBot
  • stub_fixture(path_to_yaml, **opts) to stub some classes

Loading

# 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) }

Seeding

The seed (seed_fixture) file should be a YAML/JSON with opinionated parameters, namely:

  • type for the name of the FactoryBot factory
  • traits for the factory traits
  • params 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.

Stubbing

Another opinionated format we use for stubs (stub_fixture). The gem supports stubbing both message chains and constants.

For message chains:

  • class for stubbed class
  • chain for messages chain
  • arguments (optional) for specific arguments
  • actions for an array of actions for consecutive invocations of the chain

For constants:

  • const for stubbed constant
  • value 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.

License

The gem is available as open source under the terms of the MIT License.

You can’t perform that action at this time.