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.
