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. adescribe
orcontext
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?
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.
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.
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 .
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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With