Bogdan Gusiev's blog

How to make good software for people


Ultimate rspec matcher to test validation
30 Jun 2010

After a first thousand of tests using Rspec I fount it very annoying to repeat my self testing the standard code as spec code is usually twice longer than code it test. I've started to look for a way to simplify the patterns and make it reusable. Among other nice rspec tricks there is possibility to write custom Rspec matchers. Spec the validation is just two lines of code for each attribute now.

Existing solutions

After doing some search around I found Shoulda and remarkable gems that provide a way of doing this. Here is one of remarkable variant (others are just ugly):
it { should validate_numericality_of(:age, :greater_than => 18, :only_integer => true) }
But that is huge contradiction with BDD methodology because it doesn't make you think about behavior. People use to copy-paste code from model to spec and all possible bugs within. As the result nothing is tested... I don't even say about "test first".

Easy DSL and Implementation

When I implement validation everything I care about is: what values should be accepted and what values should not. An easy DSL that realize this pattern would be:
describe User do
  it { should accept_values_for(:email, "john@example.com", "lambda@gusiev.com") }
  it { should_not accept_values_for(:email, "invalid", nil, "a@b", "john@.com") }
end
That's it! Two lines of code per attribute. And that is perfectly fine for "test first" principle.
Rspec authors take care about custom Rspec matcher. So the implementation is very easy. Fork me on github: accept_values_for rpec matcher. The accept_values_for pattern is a true BDD way and unlike Remarkable it really do testing.

validates_uniqueness_of

This is very special validation because you always need more then one record to test it. So, the pattern for uniqueness validation is:
describe User do
  context "if user with email 'aa@bb.com' exists" do
    before do
      User.create!(@valid_attributes.merge(:email => 'aa@bb.com')
    end
    it { should_not accept_values_for(:email, "aa@bb.com") }
  end
end

Summary

Custom matcher is just 50 lines of code that make your life much easier. Do not scare to write your own. Custom matcher to test ActiveRecord#named_scope coming soon.

rspec rails activerecord validation pattern