DRY up ActiveRecord specs a bit

These days I use RSpec exclusively. Well, there are those few remaining crufty test/unit tests hanging around but I digress. When writing specs for ActiveRecord models there is a particular pattern I follow. I tend not to use fixtures unless their required. Typically I just new up a model with the required data. If the model needs to exist on the database I use create instead.

With RSpec, examples tend to get broken up into several describe blocks. As a result I almost always create a helper module that gets included into the describe blocks.

Assuming a Post model, post_spec.rb might start out looking like the following:

require File.dirname(__FILE__) + '/../spec_helper.rb'
 
module PostSpecHelpers
  def default_attributes
    { :title => 'Ninjas Kick Ass',
       :body => 'Ninjas are dangerous, watch out!' }
  end
end
 
describe Post do
  include PostSpecHelpers
end
 
describe Post, 'validating' do
  include PostSpecHelpers
 
  it "should require a title" do
    post = Post.create default_attributes.merge(:title => nil)
    post.should have(1).error_on(:title)
  end
end

There is not a whole lot to DRY there. The common pattern shown is to create the object while overriding one or more attribute defaults. The tediousness of the code is pretty minor but it starts to grow old. Another thing that bothers me is RSpec knows a model is being spec’d. That seems like useful information that should be taken advantage of.

Normally I would just deal with the above. You usually want to tread carefully when abstracting things out of specs.

I did end up coming up with something. What put me over the edge was the need to create and new up models in shared specs.

For example, what if there is a Post and a WikiPage that both have a title and body? You could do inheritance but I tend not to like that. You never know how the two models will diverge. Instead I usually create a module with the shared code. I’ll then write specs that are shared by both taking advantage of it_should_behave_like.

The result of what I ended up with is very simple. Just a couple of helper methods to create (and new up) models while taking advantage of the fact that RSpec knows modles are being spec’d.

Here is the example above rewritten:

it "should require a title" do
  create_with(:title => nil).should have(1).error_on(:title)
end

Here is what I have in spec_helper.rb:

module ModelExtensions
  def new_model(overrides = {})
    self.class.described_type.new(respond_to?(:default_attributes) ? 
      default_attributes.merge(overrides) : overrides)
  end
  alias new_with new_model
 
  def create_model(overrides = {})
    self.class.described_type.create(respond_to?(:default_attributes) ? 
      default_attributes.merge(overrides) : overrides)
  end
  alias create_with create_model
end
 
Spec::Runner.configure do |config|
  # only include for Model examples
  config.include(ModelExtensions, :type => :model)
end

In the past I’ve used several more extensions, especially when it comes to specing controllers (controllers and their specs drive me nuts with there repetitive patterns). I often go back and forth. It’s a battle between DRYness and obscuring the specs. Based on experienced its almost always (just plain always?) best to err on the side of maximum readability at the expense of DRYness. Although I don’t think the above example is too bad, sometimes I just can’t help myself.

Post a Comment

Your email is never published nor shared. Required fields are marked *