Why I love RSpec nested example groups

In: Ruby|Ruby on Rails

30 Dec 2007

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.

4 Responses to Why I love RSpec nested example groups

Avatar

Shanti Braford

January 2nd, 2008 at 11am

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.

Avatar

Chu Yeow

January 3rd, 2008 at 10pm

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!

Avatar

Ruby, Rails, Rails Plugins, JavaScript, SEO « exceptionz

January 4th, 2008 at 10am

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

Avatar

Assert multiple conditions in a single test, or split into multiple tests? - PHP Solutions - Developers Q & A

December 3rd, 2013 at 10am

[…] that does is RSpec for Ruby, which allows you to set up nested example groups. For […]