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.

DRY in your functional and ActionMailer tests - Rails 2.0 a feature a day #7

That’s right, Don’t Repeat Yourself in your functional and ActionMailer tests. If you’re a Test::Unit user, this will probably look familiar to you when writing Rails functional tests (for your controllers):

class PostsControllerTest < Test::Unit::TestCase
  def setup
    @controller = PostsController.new
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new
  end

  def test_should_not_explode
    # Invariably sexy test code.
  end
end

You’d probably have noticed how setup always sets the @controller, @request and @response instance variables in all your controller tests. In Rails 2.0, you can instead subclass ActionController::TestCase (in turn a subclass of Test::Unit::TestCase) in your test cases and avoid repeating the instance variable assignments.

class PostsControllerTest < ActionController::TestCase
  # No need to define setup.

  def test_should_not_explode
    # Invariably sexy test code.
  end
end

Isn’t that much better?

Watch out for a gotcha though, when you actually do need to define a setup method (like when you need to setup several other things before your tests run), as Jeffrey Allan Hardy reports. The issue has been resolved on edge Rails. If you’re not using edge Rails, you should remember to always call super in your setup method should you define one:

class PostsControllerTest < ActionController::TestCase
  def setup
    super
    @user = users(:konata)
  end

  def test_should_not_explode
    # Invariably sexy test code.
  end
end

A similar subclass of Test::Unit, ActionMailer::TestCase is available for your ActionMailer unit tests as well. That means you can replace this:

class UserMailerTest < Test::Unit::TestCase
  include ActionMailer::Quoting

  def setup
    ActionMailer::Base.delivery_method = :test
    ActionMailer::Base.perform_deliveries = true
    ActionMailer::Base.deliveries = []

    @expected = TMail::Mail.new
    @expected.set_content_type "text", "plain", { "charset" => "utf-8" }
    @expected.mime_version = '1.0'
  end

  def test_signup
    # Test code.
  end
end

with this:

class UserMailerTest < ActionMailer::TestCase
  def test_signup
    # Test code.
  end
end

For the geeks among you, check out the related changeset.

About the contributor, Michael Koziarski

Michael Koziarski, better know as nzkoz, is a long-time Rails core committer. Michael has done a signficant amount of work improving the performance of ActiveRecord for Rails 2.0 so we have him to thank for a good portion of the optimization work done on ActiveRecord. He also keeps a fairly new personal blog.

Ruby 1.9.0 has been released

Just in the nick of time for the Christmas target, the Ruby core team has released Ruby 1.9.0 - Matz announced the release on the ruby-talk mailing list not too long ago.

Rubygem and Ruby library developers, now’s the time to get down to making your Ruby code compatible with 1.9.0.

Rails is not too far from being compatible with 1.9, thanks to the efforts of Jeremy Kemper and a few other contributors. Mongrel is not quite there yet though (I use mongrel for my Rails applications, and I think at least 50% of you do too). So while running your Rails apps on Ruby 1.9 is not an easy task right now (unless you enjoy lots of monkey patching), let’s hope the full 1.9.0 release will inspire more Ruby developers to get their libraries compatible so we can all enjoy the big speed boost in Ruby 1.9.

Very nice (if somewhat geeky) Christmas present from the Ruby core and contributors!

Generating migrations gets easier - Rails 2.0 a feature a day #6

You may not have noticed this yet, but Rails 2.0 has a new convenient syntax for generating ActiveRecord migrations. Go ahead, run the migration generator with script/generate migration. I’ll wait.

Yup, you can now specify the columns you want to add in your migration by passing attribute/type pairs to the migration generator. Ergo,

script/generate migration AddMoreToAnime episodes:integer licensed:boolean

will generate a migration like so:

class AddMoreToAnime < ActiveRecord::Migration
  def self.up
    add_column :animes, :episodes, :integer
    add_column :animes, :licensed, :boolean
  end

  def self.down
    remove_column :animes, :licensed
    remove_column :animes, :episodes
  end
end

This ties in pretty nicely with the syntax for the model generator (script/generate model), which also accepts attribute pairs. Tiny change but “it’s all about consistency”.

For the geeks among you, check out the related changeset and ticket.

About the contributor, Pratik Naik

Pratik Naik (WorkingWithRails profile), probably better known as lifofifo or just lifo, is an active Rails contributor and can be found on #rails-contrib and #rubyonrails nearly 24/7. Almost everyone who’s used Rails has used code that lifo has written. Oh, and lifo also won one of the monthly Rails Hackfests.

He also keeps a pretty good blog where he posts mostly on Rails hacking and tips.

Rails 2 a feature a day - update

In case anyone is wondering about the pause in Rails 2.0 - a feature a day posts, yes, I know, I’ve not been writing any posts for a week or so. I’ll be posting more actively again from tomorrow - just taking a short break due to general lethargy these few days. And submitting patches > blogging.

On a somewhat related note, I’ll be posting weekly updates of edge Rails changes in a tie-up with a particular Rails podcast from the beginning of next year - not gonna say which one!