This should_raise an exception

If you use Shoulda and, like me, you hate Test::Unit’s assert_raise(), I may have something of interest for you.

Why the hate?

Well, assert_raise accepts an *args list of exception types.

If you don’t pass any, you get some nonsense because an empty array doesn’t jive with the exception raised by your block. Useful. So if you don’t care what exception is raised, assert_raise isn’t gonna help you.

Also, assert_raise doesn’t let you specify what kind of exception message you’re expecting. I actually don’t mind that a given assertion should verify exactly one thing. On the other hand I have to jump through hoops to capture the exception if I want to assert on the error message.

A shoulda macro to the rescue

As usual, Shoulda is there to help us keep our test code DRY and intuitive. I’ve concocted a useful macro called should_raise. Here’s the gist:

It must be called with the block you expect to raise an exception, of course. You can also specify two optional arguments, the exception type and the message.

:kind_of or :instance_of

If not specified, an assertion is made that an exception was raised, but with no restriction on the type of the exception.

If you specify :kind_of, the assertion will be that much more precise. It will check that the exception raised was of the type specified, or a descendant.

If you specify :instance_of, the assertion is now that the exception raised was exactly of the type specified.

A shorthand is also available, where the type of the exception is supplied directly, like should_raise(LoadError), in which case the assertion is the same as with :instance_of.

In all of these cases, exactly one assertion is generated, whether or not :type is specified.

:message

If :message is specified, a second assertion will be added in order to make sure the error message matches the parameter. This can either be a string or a regex. The assertion is simply an assert_match.

If not specified, no assertion is generated for the message.

Examples

should_raise do
  require "more vespene gas"
end
# 1 assertion

should_raise(LoadError) do
  require "more vespene gas"
end
# 1 more restrictive assertion

should_raise(:instance_of => LoadError) do
  require "more vespene gas"
end
# 1 assertion, the same as should_raise(LoadError)

should_raise(:kind_of => ScriptError) do
  require "more vespene gas"
end
# 1 assertion, slightly less strict than with :instance_of (note: LoadError < ScriptError)

should_raise(:message => "no such file to load") do
  require "more vespene gas"
end
# 2 assertions

should_raise(:message => /vespene/) do
  require "more vespene gas"
end
# 2 assertions

should_raise(LoadError, :message => "such file to load") do
  require "more vespene gas"
end
# 2 assertions

should_raise(:kind_of => LoadError, :message => "file to load") do
  require "more vespene gas"
end
# 2 assertions

should_raise(:instance_of => LoadError, :message => "to load") do
  require "more vespene gas"
end
# 2 assertions

Conclusion

As you can tell, I’m eagerly awaiting Starcraft II.

No, I meant: check out the code on gist 20019. I’ve included a reasonable suite of unit tests in a comment at the end.

Feel free to use it in any way you like. Just make sure you don’t sue me.

Comments