Rspec 2 and Rails 3

January 12th, 2010

With the beta release of Rails 3 just around the corner, we’re planning a 2.0 release of Rspec, with an rspec-rails-2.0 gem for rails-3.0.

Late last week and through the weekend, Engine Yard, Relevance and Obtiva sponsored a meeting with Yehuda Katz, Carl Lerche, Chad Humphries, and me. The four of us laid out some groundwork and made some good progress toward what promises to be be a very friendly world for Rspec and Rails users and extenders.

Thanks to David Heinemeier Hansson and the rest of the Rails core team for embracing agnosticism without compromising convention over configuration.

I’ll follow up with details as things shape up, but here is a quick synopsis:

Rails users

Whether or not you use Rspec, you’ll see improvements in some of the built-in assertions, and other testing facilities that ship with Rails.

Rspec-rails users

If you do use Rspec, you’ll see a new rspec-rails plugin/gem that hooks into new features of rails-3 like the new rails generators. You’ll also see support for Merb-style request specs that wrap Rails’ integration tests.

Test framework authors

Rails’ testing APIs will be decoupled from the Test::Unit and Minitest runners. For authors of new testing frameworks, this means that you’ll be able to include a module in your framework’s objects instead of having to subclass TestCase. This will make it much easier to experiment with new ideas in the context of Rails, which clearly exposes those ideas to a wider audience than otherwise.

release candidates

We’re using the new rubygems prerelease feature to do proper release candidates. This feature was introduced to rubygems a couple of versions back, but I’d recommend updating to rubygems-1.3.5 before installing the rspec prerelease gems.

For those unfamiliar with this new rubygems feature, you have to add the --prerelease flag in order to see and install these gems:

$ gem search --remote --prerelease rspec

  *** REMOTE GEMS ***

  rspec (1.2.9.rc1)
  rspec-rails (1.2.9.rc1)

$ [sudo] gem install --prerelease rspec
$ [sudo] gem install --prerelease rspec-rails

This way only those who choose to install the prerelease gems will get them, while those who exclude the –prerelease flag will get the previous final release (rspec-1.2.8, rspec-rails-1.2.7.1).

Once you install the prerelease gems, Rubygems will treat 1.2.9.rc1 as a higher version than 1.2.8, but a lower version than 1.2.9. That way when we do the final release you’ll be able to just install 1.2.9 and it will take its rightful place ahead of 1.2.9.rc1 without you having to uninstall rc1.

I invite you to install these prerelease gems and report any issues you run into to http://rspec.lighthouseapp.com/rspec. Advanced thanks to those who help the rest by breaking these in.

changes

Here are the changelogs for both rspec and rspec-rails. I’ll post separately about some of the new features in more detail.

rspec-1.2.9-rc1

  • enhancements

    • manage backtrace-ignore patterns with Spec::Runner.configure (Martin Emde). Closes #870.
    • friendly mock argument expectation failure message (Tim Harper). Closes #868.
    • added double() as alias for stub() and mock()
    • failure messages for doubles, mocks and stubs use the right name
    • add let() method to assign memoized attributes (suggestion from Stuart Halloway). Closes #857.
    • add its method so you can say: describe Array do its(:length) { should == 0 } (Stephen Touset). Closes #833
    • spec command automatically uses spec/spec.opts if it is present (suggestion from Yehuda Katz)
    • rspec now adds PROJECT_ROOT/lib and PROJECT_ROOT/spec to the load path
    • determines PROJECT_ROOT by recursing up until it finds a directory that has a ./spec directory (thanks to Scott Taylor)
    • supports require ’spec_helper’
    • supports running specs from the PROJECT_ROOT or any directory below it
    • closes #733
  • not really a bug fix or enhancement

    • temporarily moved heckle feature to features-pending (waiting to see what happens with http://rubyforge.org/tracker/index.php?func=detail&aid=26786&group_id=1513&atid=5921)

rspec-rails-1.2.9-rc1

  • enhancements

    • added route_to and be_routable matchers (Randy Harmon). Closes #843.
    • Provide better failure message for render_template when redirected (Josh Nichols). Closes #885.
    • generated specs require ’spec_helper’
  • bug fixes

    • pass the correct args to super in controller#render depending on the rails version (Lucas Carlson). Closes #865.
    • use Rack::Utils.parse_query to convert query strings to hashes. Closes #872.
    • errors correctly bubble up when a controller spec in isolation mode requests a non-existent action/template
    • no error if either action or template exist
    • error if neither exist
    • Closes #888.
  • removals

    • spec_server has been removed in favor of spork.
    • You can still use the –drb flag, but you’ve got to install the spork gem.
    • Windows users who cannot use the spork gem can install the spec_server from http://github.com/dchelimsky/spec_server
  • http://rspec.info

  • http://rubyforge.org/projects/rspec
  • http://github.com/dchelimsky/rspec
  • http://github.com/dchelimsky/rspec-rails
  • http://wiki.github.com/dchelimsky/rspec
  • rspec-users@rubyforge.org
  • rspec-devel@rubyforge.org

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\")