before_action/after_action

November 6th, 2007

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.

[Updated on 2/25]

Have you seen this show up in your specs?

specify "should be empty" do
  @group.should be_empty
end

As of rev 1519, RSpec will now take (almost) all of the stock expectations and auto-generate spec names for you. So this:

context "A Group" do
  ...
  specify do
    @group.should be_empty
  end
end

A Group
- should be empty

This works for all of the standard expectations except those that take blocks, which would result in names like “should satisfy block”, which doesn’t seem that helpful.

Custom Expectation Matchers

Getting this to work w/ your custom matchers is a snap. Just implement #to_s in the matcher to return a String with everything after “should ” or “should not ”. For example, Equal#to_s in RSpec looks like this:

def description
  "equal #{@expected.inspect}"
end

So the following specs:

specify do
  result.should equal(3)
end

specify do result.should_not equal("this string") end

would result in the following output:

- should equal(3)
- should not equal(\"this string\")