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