minitest/minitest · error · TypeError
class or module required for rescue clause. Got %p
Error message
class or module required for rescue clause. Got %p
What it means
Minitest raises this TypeError from assert_raises when the expected-exception arguments are not Ruby classes/modules. After popping a trailing String as the custom failure message and defaulting to StandardError when no class was given, assert_raises verifies 'exp.all? Module' (assertions.rb:411) before doing 'rescue *exp'; any non-Module value left in the list aborts with this message instead of a confusing error from rescue itself.
Source
Thrown at lib/minitest/assertions.rb:411
# assert_raises(CustomError, 'This should have raised CustomError') { method_with_custom_error }
#
# Using the returned object:
#
# error = assert_raises(CustomError) do
# raise CustomError, 'This is really bad'
# end
#
# assert_equal 'This is really bad', error.message
def assert_raises *exp
flunk "assert_raises requires a block to capture errors." unless
block_given?
msg = "#{exp.pop}.\n" if String === exp.last
exp << StandardError if exp.empty?
# TODO: remove this if https://bugs.ruby-lang.org/issues/22007 gets fixed
raise TypeError, NO_RE_MSG % [exp] unless exp.all? Module
begin
yield
rescue *exp => e
pass # count assertion
return e
rescue Minitest::Assertion # incl Skip & UnexpectedError
# don't count assertion
raise
rescue SignalException, SystemExit
raise
rescue Exception => e
flunk proc {
exception_details(e, "#{msg}#{mu_pp exp} exception expected, not")
}
end
exp = exp.first if exp.size == 1View on GitHub (pinned to 581e7d5386)
Solutions
- Pass exception classes, not instances or strings: assert_raises(ArgumentError) { ... }
- To assert on the message, use the returned exception: err = assert_raises(RuntimeError) { ... }; assert_equal "boom", err.message
- If the class comes from a variable, guard it: raise ArgumentError, "#{exp.inspect} is not an exception class" unless exp.is_a?(Class)
- Remember the argument contract: one or more exception classes plus one optional trailing String message, e.g. assert_raises(CustomError, 'This should have raised CustomError') { ... }
Example fix
# before
assert_raises(RuntimeError.new) { risky_call } # => TypeError
assert_raises("connection failed", "note") { risky } # => TypeError
# after
error = assert_raises(RuntimeError) { risky_call }
assert_equal "connection failed", error.message Defensive patterns
Strategy: validation
Validate before calling
# validate assert_raises args the way minitest does (assertions.rb:407-411):
def assertable? *exp
exp = exp.dup
exp.pop if String === exp.last # trailing string = custom message
exp << StandardError if exp.empty? # default
exp.all? { |e| e.is_a?(Module) }
end
raise ArgumentError, 'pass exception classes' unless assertable?(MyError, 'msg') Type guard
# returns true only when every arg (minus a trailing message string) is a class/module
def assert_raises_args_valid?(*exp)
exp = exp.dup
exp.pop if String === exp.last
exp << StandardError if exp.empty?
exp.all? { |e| e.is_a?(Module) }
end Try / catch
begin
assert_raises(*expected) { risky }
rescue TypeError => e
raise ArgumentError, "assert_raises needs exception classes, got #{expected.inspect}" unless e.message =~ /class or module required/
raise
end Prevention
- Pass classes (ArgumentError), never instances, symbols, or message strings as the expectation
- Remember only the LAST String argument is a custom failure message
- Assert on the returned exception's .message instead of passing message strings as expectations
- For dynamically resolved classes, guard with is_a?(Class) and default to StandardError
When it happens
Trigger: Calling assert_raises with an exception instance: assert_raises(RuntimeError.new) { ... }; with a symbol: assert_raises(:timeout) { ... }; with a nil or interpolated variable: assert_raises(@error) { ... } where @error is nil; with a string that is not the LAST argument: assert_raises("boom", "note") { ... } — only the final String is consumed as the failure message, so an earlier string stays in the rescue list.
Common situations: Habit carried over from RSpec's raise_error("message text"), which accepts message strings; passing a dynamically loaded error constant that turns out nil; copy-pasting the error message instead of the error class into assert_raises.
Understand the failure class
Background: Invalid argument type errors: "must be of type string", "expected X, got Y", and ERR_INVALID_ARG_TYPE explained — this error's family across 15 libraries.
Related errors
- Skipped, no message given
- [#{category}] #{message}
- let '#{name}' cannot begin with 'test'. Please use another n
- let '#{name}' cannot override a method in Minitest::Spec. Pl
- No failures. Probably not a false positive. Aborting.
AI-assisted analysis of minitest/minitest@581e7d5386 (2026-08-23).
Data as JSON: /api/errors/effc8690e26efb2b.
Report an issue: GitHub.