What's the difference between "include_examples" and "it_behaves_like"?
In RSpec, what's the difference between it_behaves_like
and include_examples
?
The documentation says:
include_examples
— include(s) the examples in the current context
it_behaves_like "name"
— include(s) the examples in a nested context
But what does this actually mean? Replacing one with the other doesn't seem to have an effect on whether my tests pass or fail. Is there a reason to prefer one over the other in some situations?
Also, are it_should_behave_like
and it_behaves_like
just synonyms?
Solution 1:
You probably know how to use describe
, context
, it
and specify
to clearly communicate one aspect of your code. The nested context provided by it_behaves_like
can be used to improve this communication with the reader.
I will base my example on the example given in the RSpec documentation for shared examples:
shared_examples "a collection" do
context "initialized with 3 items" do
it "says it has three items" do
# ...
end
end
end
describe Array do
it_behaves_like "a collection"
include_examples "a collection"
end
If you run RSpec with --format documentation
you get the following output:
Array
behaves like a collection
initialized with 3 items
says it has three items
initialized with 3 items
says it has three items
So the difference is how the spec is read eg in case of a failure.
Which style you prefer is a question of aesthetics of how you like your specs to read. Furthermore you would suggest to always use the same style if you work in a team to improve consistency.
Also, are it_should_behave_like and it_behaves_like just synonyms?
Almost, the context is named differently. it should behave like ...
vs behaves like ...
. Again a question of aesthetics.
Solution 2:
There is a difference in case you pass parameters to the shared_examples.
It's explained very well in a warning in their doc:
WARNING: When you include parameterized examples in the current context multiple times, you may override previous method definitions and last declaration wins. So if you have this kind of shared example (or shared context)
RSpec.shared_examples "some example" do |parameter|
\# Same behavior is triggered also with either `def something; 'some value'; end`
\# or `define_method(:something) { 'some value' }`
let(:something) { parameter }
it "uses the given parameter" do
expect(something).to eq(parameter)
end
end
RSpec.describe SomeClass do
include_examples "some example", "parameter1"
include_examples "some example", "parameter2"
end
You're actually doing this (notice that first example will fail):
RSpec.describe SomeClass do
\# Reordered code for better understanding of what is happening
let(:something) { "parameter1" }
let(:something) { "parameter2" }
it "uses the given parameter" do
\# This example will fail because last let "wins"
expect(something).to eq("parameter1")
end
it "uses the given parameter" do
expect(something).to eq("parameter2")
end
end
To prevent this kind of subtle error a warning is emitted if you declare multiple methods with the same name in the same context. Should you get this warning the simplest solution is to replace include_examples with it_behaves_like, in this way method overriding is avoided because of the nested context created by it_behaves_like