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
end

Perfect, 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
end

Finally, 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)

Railscasts - Free Ruby on Rails Screencasts

Re: Test Helper: Clean, Custom Assertion Messages

Enhancement: http://pragmatig.wordpress.com/2008/04/ … e-dry-way/

working with a set of valid attributes, so that things like:
username should not be the same as password, password should match password_confirmation ... can be tested

Re: Test Helper: Clean, Custom Assertion Messages

thanks for the link....very useful and informative just like the one I saw in Web Application Mania..

Last edited by brownie cookies9 (2013-04-26 06:04:53)