RSpec 3.5 pass argument to shared_context
Asked Answered
B

2

13

I have this code that I want to reuse in several specs:

RSpec.shared_context "a UserWorker" do |user|

  let(:mock_context_user) {{
    id: 1,
    brand: user.brand,
    backend_token: user.backend_token
  }}

  before(:each) do
    allow(SomeClass).to receive(:some_method)
      .with(user.id).and_return(mock_context_user)
  end

  before(:each, context: true) do
    Sidekiq::Testing.inline!
  end

  after(:each, context: true) do
    Sidekiq::Testing.fake!
  end

end

And in the spec file that uses the shared code:

let(:user) { build :user } # FactoryGirl

...

describe '#perform' do
  # some lets here

  include_context 'a UserWorker', user

  context 'when something exists' do
    it 'does some stuff' do
      # test some stuff here
    end
  end
end

But that gives me this error:

/.rvm/gems/ruby-2.3.0@fb-cont/gems/rspec-core-3.5.1/lib/rspec/core/example_group.rb:724:in `method_missing': `user` is not available on an example group (e.g. a `describe` or `context` block). It is only available from within individual examples (e.g. `it` blocks) or from constructs that run in the scope of an example (e.g. `before`, `let`, etc). (RSpec::Core::ExampleGroup::WrongScopeError)

Suggestions? Any help is appreciated.

Bias answered 5/8, 2016 at 16:2 Comment(0)
H
0

Since it will always return the same mock_context_user, you can try something more generic like:

allow(SomeClass)
 .to receive(:some_method)
 .with(an_instance_of(Fixnum))
 .and_return(mock_context_user)

But I'm not actually sure if an_instance_of is available for RSpec 3.5, it is on RSpec 3.3.

Hazlett answered 5/8, 2016 at 17:2 Comment(1)
Thank you. I ended up setting user_id in the shared_context let!(:user_id) { rand(0..9) } instead of passing it in and the specs passed!Bias
S
16

The RSpec docs aren't very clear on this, but you can inject additional values by passing a block containing let() calls to include_context. The "customization block" passed by the spec will be evaluated first, and is available to the code declared in the shared context.

Here's a shared context that depends on the specs including it to let() a value, value_from_spec, and then sets a couple more values, one via let() and one via a before() block:

RSpec.shared_context('a context', shared_context: :metadata) do
  # assume the existence of value_from_spec
  let(:a_value_from_context) { value_from_spec - 1 }

  before(:each) do
    # assume the existence of value_from_spec
    @another_value_from_context = value_from_spec + 1
  end
end

(Note that unlike the OP's |user| example, we never explicitly declare value_from_spec, we just trust that it'll be there when we need it. If you want to make what's going on more obvious, you could check defined?(:value_from_spec) and raise an error.)

And here's a spec that injects that value, and reads the shared context's transformations of it:

describe 'passing values to shared context with let()' do
  # "customization block"
  include_context 'a context' do
    # set value_from_spec here
    let(:value_from_spec) { 1 }
  end

  describe 'the context' do
    it 'should read the passed value in a let() block' do
      expect(a_value_from_context).to eq(0)
    end

    it 'should read the passed value in a before() block' do
      expect(@another_value_from_context).to eq(2)
    end
  end
end
Shikoku answered 14/8, 2019 at 18:38 Comment(1)
Using shared_context with let should be the accepted answer, since it actually enables the behaviour of "passing arguments" to the shared contexts. :)Hazlett
H
0

Since it will always return the same mock_context_user, you can try something more generic like:

allow(SomeClass)
 .to receive(:some_method)
 .with(an_instance_of(Fixnum))
 .and_return(mock_context_user)

But I'm not actually sure if an_instance_of is available for RSpec 3.5, it is on RSpec 3.3.

Hazlett answered 5/8, 2016 at 17:2 Comment(1)
Thank you. I ended up setting user_id in the shared_context let!(:user_id) { rand(0..9) } instead of passing it in and the specs passed!Bias

© 2022 - 2024 — McMap. All rights reserved.