Shared example groups are a great feature of Rspec that help you simplify your tests and keep your code DRY. You setup shared example groups almost exactly like you would a regular set of specs, but these similarities can be slightly misleading.
Below we have an example model, spec and shared example group. Our Dog model has its own set of functionality, but as a mammal it should still have some aspects of being a mammal. We’ve got some specs in a shared example group that we use for testing all of our mammal models to make sure things don’t get too out of whack in the universe.
Our Example Model
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Dog attr_accessor :name, :mammal def initialize self.mammal = true end def greet "Hi, I'm #{self.name}, woof woof!" end end |
Our Example Spec
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | describe Dog do before(:each) do @animal = Dog.new @animal.name = "Bruno" end it_should_behave_like "a mammal" describe "Greet" do it "should respond with its name and a greeting" do @animal.greet.should == "Hi, I'm Bruno, woof woof!" end end end |
Our Shared Spec
1 2 3 4 5 | describe "a mammal", :shared => true do it "should really be a mammal" do @animal.mammal.should be_true end end |
A Typical before(:each)
Typically, when you’ve got a describe block, you might use before(:each) to setup some scenario that is used for each spec in that describe block, pretty normal RSpec stuff. We’re using it above in our example spec to create a new Dog object and set that dog’s name.
Using before(:each) in a shared spec
What if you wanted to use a before(:each) in your shared spec? Expanding on our example above we can do something like this.
1 2 3 4 5 6 7 8 | describe "a mammal", :shared => true do before(:each) do @animal.stub!(:has_body_hair?).and_return(true) end it "should really be a mammal" do @animal.mammal.should be_true end end |
Based on typical RSpec behavior, one would think that the stubbing of the has_by_hair? method on the instance of an animal, would only apply to the specs inside of the describe block of the shared example group. However, by specifying in the Dog spec that a dog “should behave like” a mammal, and thus using the shared spec, that stub will apply to all subsequent “it should” blocks in your model spec.
What if, for example, we had the following in our Dog spec.
describe "Mutate into Lizard Dog" do # dog.mutate will remove body hair and make the dog cold blooded it "should mutate into a new species" do @animal.mutate @animal.has_body_hair?.should be_false end end
If we include this in our Dog spec, below the inclusion of the shared example spec, our test will fail. We’ve already stubbed out the has_body_hair? method as part of our shared example group, when we call it down here in this completely separate describe block, RSpec is just using the stub we setup previously.
It might be a design problem if…
Now while I’m considering this a gotcha, it may be that this is expected behavior, I couldn’t find anything specifically when researching this “bug” originally. It is also possible that stubbing behavior in shared example groups is frowned upon, and I’m just “doing it wrong”.
Ultimately, I tried using patterns that made sense to me and seemed to be in line with how RSpec works in general. A stubbed method inside the before(:each) of a describe block is usually only applicable to the specs and nested describes contained within. When I realized that this is not the case with shared example groups, it seemed like a gotcha.

{ 3 comments… read them below or add one }
It appears to work as expected if you put a describe block inside the shared_examples_for block. Took me a while to figure this out!
To clarify, shared_examples_for and describe blocks are not interchangeable, what you probably really want to do is wrap your describe in an shared examples for block.
shared_examples_for “foo” do
before(:each) do
# This will get run everywhere which is not what you expect!
end
describe “#foo” do
before(:each) do
# This will get run how you expect, before only those specs in this describe block
end
it “should run the describe block above me”
# will pass
end
end
end
I just got bitten by this, thanks for sharing. Will try the shared_examples_for method above.