When RSpec meets Validatable

Jay Fields writes an excellent module called Validatable to provide a class who include the module the ActiveRecord like behavior. For example, you can say validates_presence_of :attribute etc. This page has the RDoc of the module. When I write RSpec for a Validatable class, I ended up writing a stattement like this:
   
@email.validate_only("presence_of/body")
@email.errors.on(:body).should_not be_nil
Which is basically testing body is a required field. It works, but it looks complicated and hard to understand. The RSpec on Rails plugin provides a method error_on so that I can say something like this:
 model.should have(2).errors_on(:attribute)
This is much more readable than the previous one. I came up with a small extension on Validatable so that I can have a better syntax for testing Validatable. Here's the spec:
require File.dirname(__FILE__) + '/../../../spec_helper'

module RspecValidatableSpec
class Thing
include Validatable
attr_accessor :age
attr_accessor :name
validates_presence_of :age
validates_numericality_of :age
end
end

describe RSpecValidatable, 'a validatable object' do
before :each do
@thing = RspecValidatableSpec::Thing.new
end

it "should tell you it has at least one error on a field" do
@thing.should have_at_least(1).error_on(:age)
end

it "should tell you it has one error on a required field" do
@thing.should have(1).error_on_presence_of(:age)
end

it "should tell you it has one error on a numerical field" do
@thing.should have(1).error_on_numericality_of(:age)
end
end

describe RSpecValidatable, 'a validatable object' do
before :each do
@thing = RspecValidatableSpec::Thing.new
end

it "should tell you it has no error on a field" do
@thing.should have(:no).errors_on(:name)
end
end


You may need a different path to your spec_helper.rb to make it work. I got the "traditional" errors_on just like how RSpec works on ActiveRecords. What's more, Validatable has a validates_only method which allows us validating only one type of error on one attribute. so I can have even better control than RSpec on Rails giving me over ActiveRecords. For example, I can specify what type of errors I expect to have. The only problem is that validates_only raises an error if you check an attribute on a validation that is not specified. So a nice spec like
email.should have(:no).errors_on_presence_of(:attachment)
is not quite possible. For those of you who are interested in this, here is the actual code:
module RSpecValidatable
  def errors_on(attribute)
    self.valid?
    [self.errors.on(attribute)].flatten.compact
  end

  def respond_to? method_name
    super(method_name) || method_name.to_s.index('error_on') == 0  
  end
  
  def method_missing(error_on_method, attribute)
    reason = error_on_method.to_s.sub(/error_on_/, '') 
    self.validate_only("#{reason.to_s}/#{attribute.to_s}")
    [self.errors.on(attribute)].flatten.compact
  end
  
  alias :error_on :errors_on
  
end

Validatable.send :include, RSpecValidatable 
Published Monday, November 12, 2007 10:36 AM by ywen
Filed under , ,

Comments

No Comments