Why I love RSpec nested example groups

Prior to the introduction of nested example groups in RSpec, I’d always felt that descriptions got a little unwieldy when trying to describe the different cases and disliked specifying the controller_name repeatedly. For example (and these are real examples from real projects at work, with actual code removed for conciseness):

describe 'POST HotelDealsController#create with a valid hotel deal' do
  controller_name :hotel_deals

  before(:each) do
    # ...
  end

  it "should create and save a new hotel deal" do
    # ...
  end
end

describe 'POST HotelDealsController#create with an invalid hotel deal' do
  controller_name :hotel_deals

  before(:each) do
    # ...
  end

  it "should not perform any redirection" do
    # ...
  end
end

Nested example groups allow me to do this instead:

describe SearchController do
  before(:each) do
    @search = mock_model(Search)
  end

  describe "POST 'create'" do

    it "should render 'new' when creating an invalid search" do
      # ...
    end

    describe "with a valid search that has a location_id" do
      before(:each) do
        @search.stub!(:valid?).and_return(true)
        @search.stub!(:location_id).and_return(1)
      end

      it "should save a new search that has a location_id, location_code and location_name, and redirect to 'show'" do
        # ...
      end
    end

    describe "with a valid search that doesn't have a location_id" do
      before(:each) do
        @search.stub!(:valid?).and_return(true)
        @search.stub!(:location_id).and_return(nil)
      end

      it "should ask the Location model for possible locations if the search doesn't have a location_id" do
        # ...
      end
    end
  end
end

What’s the difference you say? Well, while it may sound trivial, I can do describe SearchController only once and nest all examples for the different actions and scenarios inside without breaking it up into separate top-level example groups where I’d need to say controller_name :search multiple times.

Another (more important) benefit is I can progessively specify different scenarios I want to test by nesting them and providing a before block to setup mocks and stubs for that specific scenario. Let’s look at what I mean with some code. Over here, I’m specifying the create action of my SearchController:


describe "POST 'create'" do
  it "should render 'new' when creating an invalid search" do
    # ...
  end

  describe "with a valid search that has a location_id" do
    # ...
  end

  describe "with a valid search that doesn't have a location_id" do
    # ...
  end
end

There’re 3 possible scenarios here: an invalid search, a valid search with a location_id, and a valid search without an location_id. I can write examples for invalid searches within the top-level example group itself (it "should render 'new' when creating an invalid search"). Now, to test valid searches, I break out 2 nested example groups with their own before block to setup mock searches, one which has a location_id, the other doesn’t. What’s the big deal? It just feels much more organized than the first, non-nested example where a top-level example group is created for each scenario.

And I know it may not seem like much, and I don’t think there’s anything particularly wrong with non-nested examples - it’s a matter of personal preference. Nesting allows me to write examples for controllers in a saner fashion where I can say confidently to myself that “all examples for XXX action go here”. I’m a big believer in readable tests (if you’re on the Rails Trac much and have seen my reviews and patches you should know), so being able to write specs where I feel they belong makes me happier, and the examples read really nicely too.

I guess what I’m really trying to say is this: when trying to add a new example to legacy specs (legacy being anything that was written more than 5 mins ago) I instantly know where to place it - gone are the days of going through different example groups looking for the right place to put my new specification (and often settling on just simply creating a new top-level example group). I like to think we’ve all been there.

That said, I’m not saying that deeply nested examples are a good thing. When testing Rails controllers I find that you don’t often have to go more than 3 deep to allow for all the possible scenarios. Any more would suggest that your controller is doing too much work.

3 Comments & TrackBacks ()

Paper doll icon
Shanti Braford's Gravatar

Hi Chu Yeow,

I kept seeing your name in the Rails 2.0.2 release notes posted on the rails weblog, and wanted to say thank you for all of your contributions.

I love working in Rails, and without people like you, DHH, and the rest of rails core, I wouldn’t be able to do that!

Thanks again.

Posted by: Shanti Braford on January 2, 2008 11am

Paper doll icon
Chu Yeow's Gravatar

Aww I didn’t really contribute anything significant to Rails. Most of the good work has already been done by the core team and contributors before me!

Posted by: Chu Yeow on January 3, 2008 10pm

Paper doll icon
Ruby, Rails, Rails Plugins, JavaScript, SEO « exceptionz's Gravatar

[…] Why I love RSpec nested example groups […]

Posted by: Ruby, Rails, Rails Plugins, JavaScript, SEO « exceptionz on January 4, 2008 10am

You can subscribe to the RSS feed for comments on this post.

Sorry, this entry is no longer accepting comments. If you have something you really want to say, you can write me.