Learning RSpec

written by jared on December 30th, 2007 @ 09:59 PM

The end of 2007 has brought about some exciting changes for me, most notably a new job with Education Revolution. To try and ease the transition during a stressful holiday season, I gave myself a week off between jobs, which left me with a bit of unexpected time on my hands between Christmas and New Year's. I decided to make the most of my time off and try to learn RSpec.

RSpec is quickly becoming a darling among some of the visionaries in the Rails world. With the release of RSpec 1.1, which brings easy integration between Test::Unit style testing and Behavior Driven Development using RSpec, it seemed prudent to try it on myself and see how it fits.

On the whole I really like the idea of specifying behaviors instead of assertions, especially when coupled with doing some design-driven development. As a developer, having both a clear set of expected behaviors and a set of slides which show the application in a near-finished state, it removes a lot of the guesswork which had been plaguing me on previous projects. Also, hearing Adam Williams and John Long talk at November's Raleigh.rb meetup about doing top-down testing (starting with integration tests, then drilling down to functional and unit tests merely to handle edge cases) has turned my opinion of Integration tests on it's head.

Having come from a metaprogramming-driven Test::Unit background using Mike Clark's TestRig framework, RSpec felt like a lot more testing code. And indeed, my rake stats seems to support that, showing a 1:2.8 ratio for my training project. Finding help with RSpec has been tricky, though. There are some really great contrived examples on the RSpec homepage, but I had trouble finding more information on how to deal with presenters, nested resources, and HTTP Basic authentication. I want to show off two solutions that I cobbled together out of solutions found online.

The first problem I encountered was in dealing with the save! method in ActiveRecord. My controllers typically use the save! coupled with a rescue statement for ActiveRecord::RecordInvalid, like so:

class WidgetsController < ApplicationController

        def create
            @widget = Widget.new(params[:widget])
            @widget.save!
            respond_to do |format|
                format.html { redirect_to @widget }
            end
        rescue ActiveRecord::RecordInvalid => e
            respond_to do |format|
                format.html { render :action => 'edit' }
            end
        end

    end

Most of the RSpec examples I had come across show something like this (excerpted from Testing Controllers with RSpec):

def do_create
    post :create, :menu_item=>{:name=>"value"}
  end

  it "should save the menu item" do
    @menu_item.should_receive(:save).and_return(false)
    do_create
  end

The problem being, using save! doesn't return false on failure, it raises an exception. Fortunately, the answer was available on the RSpec-users mailing list. Now my widet_controller_spec.rb contains the following:

it "should fail to save the widget" do
      @widget.should_receive(:save!).and_raise(ActiveRecord::RecordInvalid.new(@widget))
    end

The second major stoppage I encountered was dealing with HTTP Basic authentication. The application I was building didn't require a huge complicated account/password structure, it just needed a few protected pages available to a single administrator.

class ApplicationController < ActionController::Base

        before_filter :authenticate

        def authenticate
          authenticate_or_request_with_http_basic do |username, password|
            username == 'jared' && password == 'secret'
          end
        end
    end

Suddenly, all my controller specs for actions lying behind that authenticate filter were failing. The fix lies in stubbing out the method using the controller local variable in the spec.

describe WidgetsController do

      describe "with successful admin login" do
        before(:each) do
          controller.stub!(:authenticate).and_return(true)
        end
            ...
        end
    end

Of course, I still need to come back and write an integration test which will address both the success and failure states of the authenticate method.

All in all, I'm very impressed with RSpec, and I can see why it's picking up such a following. I'm definitely going to play around with it further, but I'm not quite ready to say that I'm going to switch all my projects over; one important factor to consider is future code maintainability. The pool of talented Rails developers is small enough to begin with, adding the further requirement of finding a Rails developer who is also versed in RSpec limits that result set even further.

Comments

  • bryanl on 30 Dec 23:46

    You would be surprised how many devs out there know rspec. And if they don’t.. they should :)

  • Jared Haworth on 03 Jan 00:53

    Bryanl,

    Well, you’ve hit on the reason why I’m making an effort to learn it… it’s a good idea to stay aware of new trends, especially in a fast moving community like the Rails community.