fbelanger fbelanger - 21 days ago 4
Ruby Question

RSpec Shared Examples - Passing Data to Shared Specs

I've tried multiple ways of getting the data I need within my shared specs, however I always get undefined values.

I am doing something similar to the following:

require 'spec_helper'

describe UserAnalyticsService do

before(:each) { @user = FactoryGirl(:user) }

let(:user_query) { UserAnalyticsQuery.build(@user) }
let(:totals) { UserAnalyticsService.new(user_query) }

it_should_behave_like "an array of hashes" # What I want
end


I've tried the following:

Nested let()



shared_examples "an array of hashes" do
it { expect(array).to be_an_instance_of(Array) }
it "each element should be an instance of Hash" do
array.each { |element| expect(element).to be_an_instance_of(Hash) }
end
end


And doing:

using a let()



it_should_behave_like "an array of hashes" do
let(:array) { totals.inactive_users }
end


using instance variable



before(:each) { @array = totals.inactive_users }


Then

it_should_behave_like "an array of hashes" do
let(:array) { @array }
end


Block Params



shared_examples "an array of hashes" do |array|
it { expect(array).to be_an_instance_of(Array) }
it "each element should be an instance of Hash" do
array.each { |element| expect(element).to be_an_instance_of(Hash) }
end
end


Then

it_should_behave_like "an array of hashes", @array


All of the following results in
nil
pointer exceptions and undefined variables.

Any advice, suggestions or recommendations are welcomed, thanks in advance.

EDIT



Okay, so I've been looking deeper into
let()
and am realizing that data passed to a shared example has to existing before the transactional block.

I'm pretty sure this was my issue as I was using
before(:each)
and
let()
to pass data, however those are both undefined until we reach the example group.

Input is still very much welcomed, especially on alternatives or perspectives to help get these common specs into a shared example.

Answer

I must admit I was confused by use of rspec shared_examples and gave up on them the last time I tried to work with them, but your question inspired me to have another look.

Surprisingly it actually turned out to be very straightforward and didn't take too long at all to knock up some tests that passed - I'm either missing something fundamental in your question or the following should give you some hint at what you need to do.

The tests themselves should be self-explanatory:

require 'rails_helper'

RSpec.describe Array, type: :class do
  shared_examples 'an array of hashes' do
    it { expect(array).to be_an_instance_of(Array) }

    it 'each element should be an instance of Hash' do
      array.each { |element| expect(element).to be_an_instance_of(Hash) }
    end
  end

  describe 'with an array of hashes' do
    context 'with predefined array' do
      let(:hash) { Hash.new(name: 'hash', value: 'value') }
      let(:array) { [hash, hash, hash] }

      context 'without using shared examples' do
        it { expect(array).to be_an_instance_of(Array) }

        it 'each element should be an instance of Hash' do
          array.each { |element| expect(element).to be_an_instance_of(Hash) }
        end
      end

      context 'using shared examples' do
        it_should_behave_like 'an array of hashes'
      end
    end

    context 'when passing array to shared example' do
      let(:hash) { Hash.new(name: 'hash', value: 'value') }
      let(:myarray) { [hash, hash, hash] }

      it_should_behave_like 'an array of hashes' do
        let(:array) { myarray }
      end

      context 'with use of before(:each) block' do
        before(:each) do
          @myarray = myarray
        end

        it_should_behave_like 'an array of hashes' do
          let(:array) { @myarray }
        end
      end
    end
  end
end

There should be no reason why the following shouldn't work either:

it_should_behave_like 'an array of hashes' do
  let(:array) { totals.inactive_users }
end