ADSENSE HERE!
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.
ADSENSE HERE!
Just saved me a bunch of lines!
ReplyDeleteGreat initiative! Ty!
[...] 2010 After having a good practice on using Ultimate rspec matcher to test validation I think it’s time to implement one for testing named scopes - custom finders. Testing these [...]
ReplyDeleteYeah I've often wondered whether it's a good idea to use Remarkable. It does seem that I'm just copying and pasting code from my model to the test, with the assumption that Remarkable is doing the right thing. Interesting to think about.
ReplyDeleteA good tip, and a nice viewpoint.
ReplyDeleteWhy i use remarkable is kind of like stubbing: i do not want to test whether ActiveRecord works, i just want to test whether i have the right calls in my model.
Also i do not agree that using remarkable is not true BDD, as i clearly express behaviour of my model. While the test is very close to the actual implementation, it still is behaviour. I want that my model checks the numericality of :age, and it should be greater than 18. Isn't that behaviour?
I get what you are saying, and it is a very pure approach, but i do believe you are testing too much. As we are using ActiveRecord, a tested framework, we do not need to test the behaviour of ActiveRecord itself. If you want to be able to switch out ActiveRecord, and replace it by DataMapper or Mongo.
But i guess this also comes down to the debate about mocking and stubbing: if you mock/stub code then you increase binding and some pieces will get harder to refactor. On the other hand: you decouple your tests.
Thanks, for good comment.
ReplyDeleteYou found a right spot of the duscussion: stubs vs no stubs.
Tests should save you from mistakes. Stubs will save you from typos or 'lost during refactor' problem. And they will just spawn more maintanance as you change implementation.
I trust AR and don't want to test it again but plugins can break everything.
May be the case above is a little different because we don't suppose to change AR validation to something else, but use no stubs in this case doesn't make tests more complicated.