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