rspec rocks!

Courtenay : January 2nd, 2007

I've been playing with rspec on a project and I gotta say, I'm in love. I'm working up to a full-blown article, but if you haven't checked it out, please do. Rspec really helps you see ruby as a message-based language. You're just passing messages around. So it's easy to set up an expectation, "this object should receive this message", and then fake out a response in order to test the logic in our application. For those of you who've been meaning to get around to it, here's the executive summary. (Go read the docs, they really are excellent) h2. behaviour-based Your specs set up expectations, like

   User.should_receive(:count) 
but can also return a value

   User.should_receive(:count).and_return(0)
This helps you test your logic, since you can determine any return value you like. In case you haven't got it, that's the same as doing

  User.stub!(:count).and_return(0)
except using should_receive is like an assertion, where it'll raise if the User class never receives that message. This means stub! is well suited to setup blocks. The counter-intuitive thing for you test::unit types is that you set up the expectations before calling the method.

context "The cart controller"
  setup do
    @user = mock("user")
    @carts = mock("carts")
    @cart = mock("cart")
  end

  specify "should render checkout with cart specified correctly" do
    @user.should_receive(:carts).and_return(@carts)
    @carts.should_receive(:find).with('1').and_return(@cart)
    @cart.should_receive(:waiting_for_address_info?).and_return(true)
    
    get :checkout, :id => 1
    response.should_be_success
  end
end
Nowhere here are we hitting the model, since that's been well tested earlier. We just ensure that the model and the association gets the correct messages, and that the logic in the controller is solid. The controller code looks something like

  def checkout
    @cart = params[:id] ? @person.carts.find(params[:id]) : @person.find_unfinished_cart  
    @cart.shipping_address ||= Address.new
    @cart.begin_checkout!
    redirect_to :action => 'address', :id => @cart unless (@cart.active? or @cart.waiting_for_address_info?)
  end
Oh, and one more tip. If you're doing something like

  redirect_to :id => @cart
You'll want to stub @cart.to_param to an ID.

  @cart = mock("cart")
  @cart.stub!(:to_param).and_return('1')

4 Responses to “rspec rocks!”

  1. Jamie van Dyke Says:

    Nice summary, rspec has some nice features for structuring tests not only in your app, but in your head too.

  2. James Mead Says:

    In your statement – “The counter-intuitive thing for you test::unit types is that you set up the expectations before calling the method.” – you are mistakenly implying that mocking and stubbing is something you can only do with RSpec.

    This is completely untrue. You can use mocking frameworks like FlexMock and Mocha in conjunction with Test::Unit. In fact RSpec’s mocking framework was originally based on a Test::Unit based mocking framework called SchMock.

  3. James Mead Says:

    You might be interested in my follow-up…

  4. rick Says:

    I like the Mocha + test/spec combo myself. It lets me ease into the testing on existing apps, or break down and use rails-specific assertions if there isn’t an available bdd alternative.

    But who cares, it doesn’t take much to switch between rspec and test/spec, test/spec and rspec, whatever.

Sorry, comments are closed for this article.