Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RSpec: How to pass a "let" variable as a parameter to shared examples

The relevant fragment of spec looks like that:

let(:one_place) { create(:place) }
let(:other_place) { create(:place) }
let(:data) { "saved data" }

shared_examples "saves data to right place" do |right_place|
  it { expect(right_place.data).to eq data }
end

context "when something it saves to one place" do
  it_behaves_like "saves data to right place", one_place
end

context "when whatever it saves to other place" do  
  it_behaves_like "saves data to right place", other_place
end

And it would works perfectly with constant parameters, but in this case I receive an error:

one_place is not available on an example group (e.g. a describe or context block). It is only available from within individualexamples (e.g. it blocks) or from constructs that run in the scope of an example (e.g. before, let, etc).

How to pass a lazily created variable to shared examples in such a case?

like image 305
mpiask Avatar asked Feb 02 '18 18:02

mpiask


People also ask

What is shared examples in RSpec?

Shared examples are a good tool to describe some complex behavior and reuse it across different parts of a spec. Things get more complicated when you have the same behavior, but it has some slight variations for different contexts.

What is let in RSpec?

Use let to define a memoized helper method. The value will be cached. across multiple calls in the same example but not across examples. Note that let is lazy-evaluated: it is not evaluated until the first time. the method it defines is invoked.

What is Shared_context in RSpec?

Use shared_context to define a block that will be evaluated in the context of example groups either locally, using include_context in an example group, or globally using config. include_context .


2 Answers

From the docs, I think you need to put your let statement in a block passed to it_behaves_like:

let(:data) { "saved data" }

shared_examples "saves data to right place" do
  it { expect(right_place.data).to eq data }
end

context "when something it saves to one place" do
  it_behaves_like "saves data to right place" do
    let(:right_place) { create(:place) }
  end
end

context "when whatever it saves to other place" do  
  it_behaves_like "saves data to right place" do
    let(:right_place) { create(:place) }
  end
end
like image 87
Mitch Avatar answered Oct 05 '22 01:10

Mitch


I'd point out that what you're trying to accomplish is unfortunately not possible. It would be desirable because it makes the usage of such variables explicit. The mentioned workaround (define let where you use it_behaves_like) works but I find shared examples written like that to be confusing.

I use a shared example to make required variables explicit in shared examples:

RSpec.shared_examples "requires variables" do |variable_names|
  it "(shared example requires `#{variable_names}` to be set)" do
    variable_names = variable_names.kind_of?(Array) ? variable_names : [variable_names]
    temp_config = RSpec::Expectations.configuration.on_potential_false_positives
    RSpec::Expectations.configuration.on_potential_false_positives = :nothing

    variable_names.each do |variable_name|
      expect { send(variable_name) }.not_to(
        raise_error(NameError), "The following variables must be set to use this shared example: #{variable_names}"
      )
    end

    RSpec::Expectations.configuration.on_potential_false_positives = temp_config
  end
end

Use it like this:

RSpec.shared_examples "saves data to right place" do
  include_examples "requires variables", :right_place

  # ...
end

context "..." do
  it_behaves_like "saves data to right place" do
    let(:right_place) { "" }
  end
end
like image 20
thisismydesign Avatar answered Oct 05 '22 00:10

thisismydesign