This is Part 2 of the preview for "Keeping Tests Dry" at next week's erubycon.
Several people offered improvements to the bad code from Part 1. Here is my own second cut, much better than the first:
# Still tests ActiveRecord as well as your code, but at least
# no exception handling
# tests only one attribute
# not (as) fragile
def test_name_validation
c = Contact.new
assert(!c.valid?)
assert_equal("can't be blank", c.errors['name'])
end
This could still be a lot better. How would you DRY this up for use across a large codebase? (More to follow...)
Comments
‘assert(!c.valid?)’ is a superfluous assertion. Simply call ‘c.valid?’ to add the errors to the model.
Also, it would be less brittle to simply assert the presence of an error on c.name:
assert_not_nil(c.errors_on(:name))
Admittedly, this assertion would still pass if the errors were caused by other validations on c.name (i.e. minimum length, letters only, etc.), but I think it’s a step in the right direction.
How about simply asserting the presence of the name key in the errors hash (and that it isn’t blank)?
Using RSpec, and a custom matcher:
c = Contact.new c.should be_invalid_without_a(:name)Using RSpec, and a custom matcher:
c = Contact.new c.should be_invalid_without_a(:name)I’ve been experimenting with setting my validations like:
So I could write:
validates_presence_of :name, :message => VALIDATION_ERRORS[ :no_name ]
and in the above you could write: assert_equal(Contact::VALIDATION_ERRORS[:no_name], c.errors[‘name’])
Also the code above makes assertions that Contact doesn’t set a default name, perhaps: c = Contact.new( :name => nil ) would be more explicit? Perhaps also, in a separate test, try c = Contact.new( :name => ”” ) ?
+1 on what Luke said.
If you weren’t using a custom matcher (however, you really should one), you could just load up the Contact instance with valid attributes, minus the name, and assert the validity of that.
I normally use test/spec, maybe I should switch to rspec. Anyway you could build a valid contact or grab a fixture, I chose the fixture route below.
RequiredAttrs = { :name => :blank, :email => [ :blank, :taken ] } RequiredAttrs.each do |attr_name, error| define_method("test_#{attr_name}_validation") do c = contacts(:joe) orig = c.send(attr_name) attr_assign = :"#{attr_name}=" c.send(attr_assign, nil) assert !c.valid? case error when String assert_equal c.errors.on(attr_name), ActiveRecord::Errors.default_error_messages[error] when Enumerable errors = error.map {|e| ActiveRecord::Errors.default_error_messages[e] }.sort assert_equal c.errors.on(attr_name).sort, errors end c.send(attr_assign, orig) assert c.valid? end end