{"record":{"id":"effc8690e26efb2b","repo":"minitest/minitest","slug":"class-or-module-required-for-rescue-clause-got-p","errorCode":null,"errorMessage":"class or module required for rescue clause. Got %p","messagePattern":"class or module required for rescue clause\\. Got %p","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"lib/minitest/assertions.rb","lineNumber":411,"sourceCode":"    #   assert_raises(CustomError, 'This should have raised CustomError') { method_with_custom_error }\n    #\n    # Using the returned object:\n    #\n    #   error = assert_raises(CustomError) do\n    #     raise CustomError, 'This is really bad'\n    #   end\n    #\n    #   assert_equal 'This is really bad', error.message\n\n    def assert_raises *exp\n      flunk \"assert_raises requires a block to capture errors.\" unless\n        block_given?\n\n      msg = \"#{exp.pop}.\\n\" if String === exp.last\n      exp << StandardError if exp.empty?\n\n      # TODO: remove this if https://bugs.ruby-lang.org/issues/22007 gets fixed\n      raise TypeError, NO_RE_MSG % [exp] unless exp.all? Module\n\n      begin\n        yield\n      rescue *exp => e\n        pass # count assertion\n        return e\n      rescue Minitest::Assertion # incl Skip & UnexpectedError\n        # don't count assertion\n        raise\n      rescue SignalException, SystemExit\n        raise\n      rescue Exception => e\n        flunk proc {\n          exception_details(e, \"#{msg}#{mu_pp exp} exception expected, not\")\n        }\n      end\n\n      exp = exp.first if exp.size == 1","sourceCodeStart":393,"sourceCodeEnd":429,"githubUrl":"https://github.com/minitest/minitest/blob/581e7d5386def72040e49b036ba49e36e9cb14df/lib/minitest/assertions.rb#L393-L429","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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') { ... }"],"exampleFix":"# before\nassert_raises(RuntimeError.new) { risky_call }         # => TypeError\nassert_raises(\"connection failed\", \"note\") { risky }  # => TypeError\n\n# after\nerror = assert_raises(RuntimeError) { risky_call }\nassert_equal \"connection failed\", error.message","handlingStrategy":"validation","validationCode":"# validate assert_raises args the way minitest does (assertions.rb:407-411):\ndef assertable? *exp\n  exp = exp.dup\n  exp.pop if String === exp.last      # trailing string = custom message\n  exp << StandardError if exp.empty?  # default\n  exp.all? { |e| e.is_a?(Module) }\nend\n\nraise ArgumentError, 'pass exception classes' unless assertable?(MyError, 'msg')","typeGuard":"# returns true only when every arg (minus a trailing message string) is a class/module\ndef assert_raises_args_valid?(*exp)\n  exp = exp.dup\n  exp.pop if String === exp.last\n  exp << StandardError if exp.empty?\n  exp.all? { |e| e.is_a?(Module) }\nend","tryCatchPattern":"begin\n  assert_raises(*expected) { risky }\nrescue TypeError => e\n  raise ArgumentError, \"assert_raises needs exception classes, got #{expected.inspect}\" unless e.message =~ /class or module required/\n  raise\nend","preventionTips":["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"],"tags":["minitest","assertions","assert-raises","type-error","ruby"],"backgroundTag":"invalid-argument-type","analyzedSha":"581e7d5386def72040e49b036ba49e36e9cb14df","analyzedAt":"2026-08-23T11:44:02.076Z","schemaVersion":2},"datasetVersion":"2026-08-23T16:17:53.355Z"}