Matt's Blog

How to test [something] with Rspec

by Matt Hodan on October 10, 2011

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 like 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 Ruby on Rails Models

All of the tests for your Ruby on Rails 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 method/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, as it is already well tested).


comments powered by Disqus

Recent Articles