I’ve slowly been learning how to test my Rails projects with Rspec and I thought it would be helpful to document this experience. I’m sure there are better, faster, smarter ways to test these concepts, so please do tell me in a comment so I (and others) can learn!
To get started, make sure you have Rspec installed properly. Ryan Bates created a great Railscast that walks through setting up Rspec.
I’m going to add to this post as I create tests over the coming weeks/months. Please be patient, and don’t forget to comment if you see something that could be done better, faster, smarter!
Understanding Rspec Organization
One of my favorite features of Rspec is that it helps you easily organize your tests. Here is an example of how a simple set of tests might be organized:
describe SourcesController do describe "#index" do before :each do @request.env['HTTP_AUTHORIZATION'] = "Basic #{ActiveSupport::Base64::encode64('user:secret')}" end it "should have this action" do get :index response.should be_success end it "should require auth to access" do @request.env['HTTP_AUTHORIZATION'] = nil get :index response.status.should == 401 end end end
Rspec gives us the keyword “describe” so we can group related tests. In the above example, I’m testing the index action of the SourcesController. If I wanted to add a new action, I might create a new “describe” block called “#new”. There are no formal organization rules here (as far as I can tell), this is just my NooB convention. Post a comment if you have a best practice that I should be using!
Rspec Expectations and Matchers
In Rspec, there are a couple of concepts that will get you 80% of the way to where you need to be, then there are about a hundred concepts that get you the final 20%. To get 80%, you should start with:
object.should object.should_not
Rspec’s Spec::Expectations module adds the “#should” and “#should_not” methods to Ruby’s Object class. These methods are the key to how Rspec works. Every object in your tests will now have these two expectation methods, which you can use to verify a value.
With #should and #should_not in our arsenal, we can go a step further and start to think about matchers. The following is a list of positive matchers. Most also have a negative equivalent (i.e. “not_equal”, “not_be_close”, etc.). You can use these matchers to make Rspec code read like a novel, which is one of the things I LOVE about Rspec.
object.should equal [value] object.should be_close [value], [tolerance] object.should be [value] object.should be_between([value-x], [value-y]) object.should predicate [optional args] object.should match [regex] object.should be_an_instance_of [class] object.should be_a_kind_of [class] object.should respond_to [symbol|string] object.should include [object] object.should have([number]).things object.should have_at_most([number]).things object.should have([number]).errors_on([symbol])
To test for exceptions and changes:
lambda {[do something]}.should raise_error
lambda {[do something]}.should raise_error([exception] [, optional message])
lambda {[do something]}.should change([instance], [method]).from([x]).to([y])
expect {[do something]}.to change([instance], [method_as_symbol]).from([x]).to([y])Testing Models
All of the tests for your models reside in the “spec/models” folder, which the Rails generator creates when you create a new model. In here, you should see a file named “[yourmodel]_spec.rb” for each model you’ve created with a Rails generator since installing Rspec.
A good rule of thumb for testing models is that you should have at least one test for every property of your model. You should also test things like validations, accessibility, and filters. But, you should only test your code (i.e. don’t test ActiveRecord, it’s already well tested).
To be continued…