Nested Example Groups 10

Posted by David Thu, 29 Nov 2007 08:58:18 GMT

Since rspec first appeared on the scene, users have been asking for nested example groups. Well it has finally arrived. RSpec 1.1.0 will ship with support for nesting, so you’ll be able to do things like this:


describe RSpec do
  before(:each) do
    @rspec = RSpec.new
  end

  describe "at release 1.0.8" do
    before(:each) do
      @rspec.version = "1.0.8" 
    end

    it "should not support nested example groups" do
      @rspec.should_not support_nested_example_groups
    end
  end

  describe "at release 1.1.0" do
    before(:each) do
      @rspec.version = "1.1.0" 
    end

    it "should support nested example groups" do
      @rspec.should support_nested_example_groups
    end
  end
end

This will output:

RSpec at release 1.0.8
- should not support nested example groups

RSpec at release 1.1.0
- should support nested example groups

If you’re using trunk, you can do this now with revision 3009 or later.

Happy nesting!

before_action/after_action 11

Posted by David Tue, 06 Nov 2007 16:55:00 GMT

A while back there was either a feature request in the rspec tracker, or a suggestion on one of the rspec mailing lists, for a feature to DRY up controller specs.

The idea was that this pattern feels a bit clunky:

describe PersonController, "handling failed POST to create" do
  def do_post
    post :create, invalid_arguments
  end

  it "should redisplay the create form" do
    do_post
    response.should render_template("people/new")
  end

  it "should try to create a Person" do
    Person.should_receive(:create).with(invalid_arguments).and_return(false)
    do_post
  end
end

And it would be nice to have something that was more expressive using tags like this:

describe PersonController, "handling failed POST to create" do
  def do_post
    post :create, invalid_arguments
  end

  it "should redisplay the create form", :after => :do_post do
    response.should render_template("people/new")
  end

  it "should try to create a Person", :before => :do_post do
    Person.should_receive(:create).with(invalid_arguments).and_return(false)
  end
end

I didn’t add this to rspec_on_rails because I personally find it harder to read. It also doesn’t support situations where you want to stub something before the action and then set a state-based expecation after the action.

But the problem is still present, and it would be nice to have something a bit less clunky.

Well – here’s what I’ve been experimenting with. This is NOT part of RSpec, and I may not want to include it in RSpec because I think it’s somewhat particular to my personal style (as opposed to a style that I think is “right” for BDD), but it’s easy enough to add to your own projects.

Here’s what the specs look like:

describe PersonController, "handling failed POST to create" do
  def do_post
    post :create, invalid_arguments
  end

  it "should redisplay the create form" do
    after_post do
      response.should render_template("people/new")
    end
  end

  it "should try to create a Person" do
    during_post do
      Person.should_receive(:create).with(invalid_arguments).and_return(false)
    end
  end
end

I really like this even though it actually turns out to be a bit more verbose. I think it speaks very clearly about what is going on – especially “during_post”, which describes very well when the Person.should_receive the :create message.

Here’s the code in spec_helper.rb that supports this pattern:

[:get, :post, :put, :delete, :render].each do |action|
  eval %Q{
    def before_#{action}
      yield
      do_#{action}
    end
    alias during_#{action} before_#{action}
    def after_#{action}
      do_#{action}
      yield
    end
  }
end

This supports controller and view specs (hence including :render).

Please try it out and let me know what you think.

RubyConf slides 1

Posted by David Mon, 05 Nov 2007 13:48:00 GMT

Here are the slides from the presentation that Dave Astels and I did at RubyConf 07.