RSpec Shared Example before(:each) Gotcha

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.

Comments

  1. says

    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

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>