Topic: Test Helper: Clean, Custom Assertion Messages
In the previous article we ended up with this custom assertion:
def assert_invalid_value(model_class, attribute, value)
if value.kind_of? Array
value.each { |v| assert_invalid_value model_class, attribute, v }
else
record = model_class.new(attribute => value)
assert !record.valid?, "#{model_class} expected to be invalid when #{attribute} is #{value}"
assert record.errors.invalid?(attribute), "#{attribute} expected to be invalid when set to #{value}"
end
end
This works fine, but when the assertion fails we get this extra "<false> is not true" message at the end. Fore example:
quantity expected to be invalid when set to -1.
<false> is not true.
This second message is being generated by the call to "assert":
assert record.errors.invalid?(attribute), "#{attribute} expected to be invalid when set to #{value}"While checking out the Test::Unit documentation to see if there's some way to suppress this message, I noticed there's another method called assert_block which all assertions are based upon. Hey! That sounds like exactly what we want. How it works is it raises an assertion with the given message if the block returns false. Let's try it out:
assert_block "#{attribute} expected to be invalid when set to #{value}" do
record.errors.invalid? attribute
endPerfect, now we don't get that second error message.
If you notice, most assertion messages enclose the input variables in angled brackets <like this>. Let's do the same in our message and include the model name in there as well so it's a little more descriptive:
assert_block "<#{model_class}.#{attribute}> expected to be invalid when set to <#{value}>" do
record.errors.invalid? attribute
endFinally, in our custom assertion we had two assertions, but the second one should catch anything the first one would have, so we can remove the first one. The end result looks like this:
def assert_invalid_value(model_class, attribute, value)
if value.kind_of? Array
value.each { |v| assert_invalid_value model_class, attribute, v }
else
record = model_class.new(attribute => value)
assert_block "<#{model_class}.#{attribute}> expected to be invalid when set to <#{value}>" do
record.valid? # Must be called to generate the errors
record.errors.invalid? attribute
end
end
end
And the error message now looks like this:
<LineItem.quantity> expected to be invalid when set to <-1>
Much better.
In the previous article I had an idea for another custom assertion. Here it is:
assert_invalid_attributes LineItem, :quantity => [-1, 'foo', 2.5], :product => nil
This is surprisingly easy to make if we use our assert_invalid_value method. We just have to loop through the given hash and call that method for each entry. Here's the code:
def assert_invalid_attributes(model_class, attributes)
attributes.each_pair do |attribute, value|
assert_invalid_value model_class, attribute, value
end
end
Oh Ruby, how do I love thee? Let me count the ways...
Last edited by ryanb (2006-09-28 12:48:41)