1

Using MiniTest spec, I can test that code raises a specific exception as follows:

proc { foo.do_bar }.must_raise SomeException

But, I don't care what the specific exception is, I just want to verify that some exception is thrown. If I, or another developer, decides to change what exception is thrown by Foo#do_bar, my test wouldn't have to change if the expected exception was specified generally enough.

That is, I would like to write the test this way (Exception is an ancestor of class SomeException):

proc { foo.do_bar }.must_raise Exception

By this results in a failure when I run the test:

[Exception] exception expected, not
Class: <SomeException>

Can I write my Minitest spec more generically with regards to exceptions?

(The actual reason I want to check for any exception, rather than a specific exception, is that I'm using a third party Gem, and it is that code that raises the exception. In fact, my method A gets called by third party method B. A raises MyException, however B catches that exception, and re-raises a different exception. This exception has the same message as my exception [and this message is something I ought to verify in the test], but a different class.)

Jimothy
  • 9,150
  • 5
  • 30
  • 33
  • 1
    So you don't care if it was `SystemStackError` or `SystemExit` or `ThreadError`? Then why bother testing? You should test for specifics. Otherwise how do you communicate to the other developers what your intent was? – vgoff Jul 24 '13 at 04:09
  • @vgoff: For brevity, I left this out, but I'm working with the Ruby Racer, which binds JavaScript to Ruby and vice versa. If my Ruby code raises an exception, this exception ends up as a RR class, V8::Error, rather than the exception my code raised. This has to do with the internals of the third party Gem I'm using, and these are details I'm not concerned about from a testing, or even implementation point of view. From the testing perspective, what is important is that my Ruby code detects the invalid condition and an exception is raised. – Jimothy Jul 25 '13 at 02:19
  • @vgoff While it changes the "why," it doesn't change the "how" as far as I can tell. You raise a good point with your first comment, in that in general, one should be specific while testing. That said, I don't want to make my question overly specific, as there may be others who, for whatever reason, wish to test for any exception using MiniTest. I will, however, add explanatory text to my question why I would like to do this. – Jimothy Jul 25 '13 at 02:48

2 Answers2

2
describe 'testing' do
  it 'must raise' do
   a = Proc.new {oo.non_existant}
   begin
     a[]
   rescue => e
   end
   e.must_be_kind_of Exception
  end
end

Regardless, this should do pretty close to what you are asking for.

vgoff
  • 10,980
  • 3
  • 38
  • 56
0

This seems odd behaviour.

From: http://bfts.rubyforge.org/minitest/MiniTest/Assertions.html#method-i-assert_raises

# File lib/minitest/unit.rb, line 337
def assert_raises *exp
  msg = "#{exp.pop}\n" if String === exp.last

  should_raise = false
  begin
    yield
    should_raise = true
  rescue MiniTest::Skip => e
    details = "#{msg}#{mu_pp(exp)} exception expected, not"

    if exp.include? MiniTest::Skip then
      return e
    else
      raise e
    end
  rescue Exception => e
    details = "#{msg}#{mu_pp(exp)} exception expected, not"
    assert(exp.any? { |ex|
             ex.instance_of?(Module) ? e.kind_of?(ex) : ex == e.class
           }, exception_details(e, details))

    return e
  end

  exp = exp.first if exp.size == 1
  flunk "#{msg}#{mu_pp(exp)} expected but nothing was raised." if
    should_raise
end

This checks the exception passed is an instance of Module and if so uses e.kind_of?(ex) which would work fine as as instance of SomeException will be of kind Exception BUT only if ex is a Module, so Exception won't work. It needs to be something common that you have mixed into your exceptions.

(As shown here http://ruby-doc.org/core-2.0/Object.html#method-i-kind_of-3F)

This matches minitests own tests ...

  module MyModule; end
  class AnError < StandardError; include MyModule; end

  ....

  def test_assert_raises
    @tc.assert_raises RuntimeError do
      raise "blah"
    end
  end

  def test_assert_raises_module
    @tc.assert_raises MyModule do
      raise AnError
    end
  end

(From: https://github.com/seattlerb/minitest/blob/master/test/minitest/test_minitest_unit.rb )

So.. if your Exception mixes in a module, you can assert on the module.. but other than that go with @vgoff's answer.. or extend minitest to do what you want.

Note: I love that ruby is all open source!

Nigel Thorne
  • 21,158
  • 3
  • 35
  • 51