104

What is the expected syntax for checking exception messages in MiniTest's assert_raises/must_raise?

I'm trying to make an assertion something like the following, where "Foo" is the expected error message:

proc { bar.do_it }.must_raise RuntimeError.new("Foo")
toro2k
  • 19,020
  • 7
  • 64
  • 71
kfitzpatrick
  • 2,425
  • 4
  • 19
  • 11

4 Answers4

171

You can use the assert_raises assertion, or the must_raise expectation.

it "must raise" do
  assert_raises RuntimeError do 
    bar.do_it
  end
  ->     { bar.do_it }.must_raise RuntimeError
  lambda { bar.do_it }.must_raise RuntimeError
  proc   { bar.do_it }.must_raise RuntimeError
end

If you need to test something on the error object, you can get it from the assertion or expectation like so:

describe "testing the error object" do
  it "as an assertion" do
    err = assert_raises RuntimeError { bar.do_it }
    assert_match /Foo/, err.message
  end

  it "as an exception" do
    err = ->{ bar.do_it }.must_raise RuntimeError
    err.message.must_match /Foo/
  end
end
blowmage
  • 8,854
  • 2
  • 35
  • 40
  • Cool, I get that. However, I'm still lost on how to make an assertion on the raised error's message. – kfitzpatrick Jan 21 '13 at 21:42
  • 3
    err = ->{ bar.do_it }.must_raise RuntimeError syntax did not work for me, It kept raising the following exception. NoMethodError: undefined method `assert_raises' for nil:NilClass – thanikkal Oct 13 '15 at 18:52
  • 2
    @thanikkal Make sure you are using `Minitest::Spec` and not `Minitest::Test`. The Spec DSL, including expectations, are only available when using `Minitest::Spec`. – blowmage Oct 10 '16 at 15:28
39

To assert exception:

assert_raises FooError do
  bar.do_it
end

To assert exception message:

As per API doc, assert_raises returns the exception matched so you can check the message, attributes, etc.

exception = assert_raises FooError do
  bar.do_it
end
assert_equal('Foo', exception.message)
Jing Li
  • 14,547
  • 7
  • 57
  • 69
8

Minitest does not provide (yet) you a way to check the actual exception message. But you could add a helper method that does it and extend ActiveSupport::TestCase class to use everywhere in your rails test suite, e.g.: in test_helper.rb

class ActiveSupport::TestCase
  def assert_raises_with_message(exception, msg, &block)
    block.call
  rescue exception => e
    assert_match msg, e.message
  else
    raise "Expected to raise #{exception} w/ message #{msg}, none raised"
  end
end

and use it in your tests like:

assert_raises_with_message RuntimeError, 'Foo' do
  code_that_raises_RuntimeError_with_Foo_message
end
Developer
  • 983
  • 12
  • 12
  • 2
    True that Minitest doesn't support checking the error message, however it can be achieved using `must_raise` because it gives you the instance of the error so you can check the message yourself. – bithavoc Sep 20 '14 at 14:28
  • 1
    This solution feels better to me, but I've not used ``must_raise`` before. – Michael Mulich Apr 17 '15 at 19:19
  • I think that this solution will not fail if no Exception is raisen. You just check raised exception to be the correct one. But if no exception is raised, no assertion is done => no errors. – Foton May 26 '16 at 13:50
  • good point @Foton I changed the answer to reflect that expectation. – Developer May 27 '16 at 18:30
  • I was doing this to assert error messages too https://github.com/noraj/nvd_api/blob/cf5127568b274e031d1cba4b1afd6856d8cfbb42/test/test_nvd_feed_api.rb#L54-L74 – noraj Jan 04 '22 at 19:54
0

To add some more recent developments, there have been some discussions on adding assert_raises_with_message to minitest in the past without much luck.

Currently, there is a promising pull request waiting to be merged. If and when it gets merged, we will be able to use assert_raises_with_message without having to define it ourselves.

In the meanwhile, there is this handy little gem named minitest-bonus-assertions which defines exactly that method, along with few others, so that you can use it out of the box. See the docs for more information.

Kostas Rousis
  • 5,918
  • 1
  • 33
  • 38