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.

Dan Manges said

Apr 15, 2008 @ 11:55 PM

Here are some other alternatives to using fixtures: http://errtheblog.com/post/7708 (Scenarios) http://www.dcmanges.com/blog/38 (Factory)

Matt Lins said

Apr 15, 2008 @ 11:55 PM

I just wanted to add a quote from David Chelimsky (rSpec Core Dev), from the mailing list, when asked what the "right" way was to provide sample data for model tests: "The "right" way doesn't really exist. It depends largely on the nature and complexity of your app. That said, my personal preference is to avoid fixtures and just create what I need in each spec. That's because I like to see everything I need to understand the context. This approach creates more duplication in specs, but less binding between them. For me, the isolation is *usually* more important than the duplication. "

John said

Apr 15, 2008 @ 11:55 PM

Sure would be nice to see the connection between @post.should have(1).error_on(:title) and @post.attributes = valid_post_attributes.except(:title) and I guess the Post model validates class method. Thanks

Matt Lins said

Apr 15, 2008 @ 11:55 PM

John: I updated the post to include the Model that the spec would be written for. That is what you were looking for, no?

Evgeny said

Apr 15, 2008 @ 11:55 PM

I don't really see much point in making an alias of Hash.merge into a Hash.with -- most ruby developers with recognize Hash.merge right away. Also ... there is the alias method for aliasing, why do you re-define a method with "def" and all?

Wahoo said

Apr 15, 2008 @ 11:55 PM

Thank you for sharing!

matt said

Apr 15, 2008 @ 11:55 PM

awesome post, just got started with rspec yesterday and being able to avoid those nasty fixtures in a rather elegant way really made by day!

Installing Wikipedia, part 3 of N, using ActiveReco said

Apr 15, 2008 @ 11:55 PM

[...] into a round hole, when I decided to see how Rspec users felt about fixtures. Turns out they are not particularly fond of them.  One way to do load test code w/o fixtures is to include model specific helper classes to load [...]

Is BDD the new TDD? Adventures with RSpec said

Apr 15, 2008 @ 11:55 PM

[...] around with rspec I'm a newbie, and am still getting used to the way other users avoid fixtures,  how and when to use mocks, stubs, or both, but so far I think it has gone a long ways toward [...]

Nick Hoffman said

Aug 22, 2008 @ 01:04 PM

@Evgeny: He probably created Hash#with simply because it's slightly closer to readable English than Hash#merge. I'd do as you suggested and simply alias Hash#merge . Cheers, Nick

mesanarne said

Sep 02, 2008 @ 06:10 PM

wow :) its very reasonable point of view. Good post. realy good post thank you ;)

RSS feed for comments on this post

Leave a Comment