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