home icon contact icon rss icon

By: Matt Lins

Lose the fixtures with rSpec

rSpec is all about writing easy to read, self-explanatory specs (tests to you TDDr's). It's tempting to want to use fixtures or even come up with a clever way to provide sample data for testing models. I'll demonstrate, what I consider to be the most beautiful way to write specs. Some of my influences have been the rSpec Mailing list and the PeepCode Screencast. I'll warn you brilliant ruby programmers: this is by no means efficient, DRY code. It is code that any stranger could come along and figure out exactly what the original developer was trying to accomplish.

Assume we have a simple Model like this:

1
2
3
4
5
6
class Post < ActiveRecord::Base

  validates_presence_of :title
  validates_length_of :title, :minimum => 2

end

First we'll need the help of a couple of additional methods in Hash. Add this to your spec_helper.rb in the root of your /spec directory.

1
2
3
4
5
6
7
8
9
10
11
12
13
# Taken from 
# http://wincent.com/knowledge-base/Fixtures_considered_harmful%3F
class Hash
  # for excluding keys
  def except(*exclusions)
    self.reject { |key, value| exclusions.include? key.to_sym }
  end

  # for overriding keys
  def with(overrides = {})
    self.merge overrides
  end
end

These two methods with allow us to create a "valid_attributes" Hash and then tweak it later on. We're going to create a helper module at the top of the spec. This example is from one of my specs for a model: Post.

1
2
3
4
5
6
7
8
9
10
require File.dirname(__FILE__) + '/../spec_helper'
module PostSpecHelper
  def valid_post_attributes
    {
      :title => "rSpec is Great"
      :body => "rSpec is a fun way to write documentation 
        and test your application at the same time"
    }
  end
end

Now we have some nice sample data. We can use this helper(don't forget to include it) in the before(setup) block of each of our describe blocks. Let start with a simple valid Post.

1
2
3
4
5
6
7
8
9
10
describe Post do
  include PostSpecHelper
  before(:each) do
    @post = Post.new
    @post.attributes = valid_post_attributes
  end
  it do
    @post.should be_valid
  end
end

Great. Now let's use the add Hash#with to test one of our validations.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
describe Post, "with a title that is 1 character long" do
  include PostSpecHelper
  before(:each) do
    @post = Post.new
    @post.attributes = valid_post_attributes.with(:title => "r")
  end

  it do
    @post.should_not be_valid
  end

  it "should have an error on title" do
    @post.should have(1).error_on(:title)
  end
end

Now, we'll use the Hash#except to test another validation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
describe Post, "with a blank title" do
  include PostSpecHelper
  before(:each) do
    @post = Post.new
    @post.attributes = valid_post_attributes.except(:title)
  end

  it do
    @post.should_not be_valid
  end

  it "should have an error on title" do
    @post.should have(1).error_on(:title)
  end
end

There you have it, an easy to read spec. Any developer could come along and figure out exactly what I was testing. The sample data is provided in the top of every spec. I don't have to go searching through fixtures to figure out why this test failed.