Written by Jared Haworth on
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.