With Minitest, how does assert_raises decide whether a test passes, what does it return, and how do you check the error message?
answer
- needs a block
- matches subclasses, like rescue
- no class given means StandardError
- returns the exception object
- no refute_raises: just call the code
basics
~10 sassert_raises(ErrorClass) { code } passes when the block raises that class or a subclass, and returns the exception so you can assert on its message. No exception, or a different one, fails the test.
solid answer
~40 s`assert_raises` needs a block and takes one or more exception classes, plus an optional trailing String used as a failure message; with no class it expects `StandardError`. It rescues with Ruby's `rescue` semantics, so a **subclass** also matches: `assert_raises(StandardError)` passes for an `ArgumentError`. On a match it **returns the exception**, which is how you check details: `error = assert_raises(ArgumentError) { calc.fine_for(-1) }` then `assert_equal "days late must be >= 0", error.message` or `assert_match`. If nothing is raised it fails with `ArgumentError expected but nothing was raised.`; if a different exception escapes it fails and shows that exception. There is no `refute_raises`: to assert that code does not raise, just call it, because an unexpected exception already turns the test into an Error.
code
ruby · 12 linesdef test_rejects_negative_days
error = assert_raises(ArgumentError) { @calc.fine_for(-1) }
assert_match(/must be >= 0/, error.message)
end
# Passes even with the wrong message: the string is only
# printed if no ArgumentError is raised.
def test_weak_version
assert_raises(ArgumentError, "days late must be >= 0") do
@calc.fine_for(-1)
end
endgo deeper
Recall that assert_raises takes a block and the expected class, and returns the exception you can check with assert_equal or assert_match.
Explain rescue-style subclass matching, the default StandardError, the trailing String as a failure message, and why there is no refute_raises.
Push for narrow error classes and message or attribute checks, and spot tests that pass on any crash because they expect StandardError.
Treat error tests as part of an API contract: decide which failure classes and messages a library promises callers, and test exactly those.
## The contract `assert_raises *exp` is Minitest's assertion for code that is supposed to fail. Its source is short enough to reason about directly: 1. It **requires a block**; without one it fails with `assert_raises requires a block to capture errors.` 2. If the last argument is a **String**, it is popped off and used as a custom failure message. 3. If no class is left, it expects **`StandardError`**. 4. Since Minitest 6.0.5, anything left that is not a class or module raises `TypeError`. 5. It yields to the block inside `rescue *exp => e`, and on a match counts one assertion and **returns `e`**. ```ruby def test_rejects_negative_days error = assert_raises(ArgumentError) { @calc.fine_for(-1) } assert_equal "days late must be >= 0", error.message end ``` ## What matches Because the matching is a plain Ruby `rescue`, it follows `rescue` rules: - **Subclasses match.** `assert_raises(StandardError) { Integer("x") }` passes, since `ArgumentError < StandardError`. That makes very broad classes weak assertions: `assert_raises(StandardError)` also passes on a `NoMethodError` from a typo in the code under test. - **Several classes** can be listed: `assert_raises(KeyError, IndexError) { ... }` passes on either. - To pin the **exact class**, add `assert_instance_of ArgumentError, error` on the returned object. | block does | result | |---|---| | raises the listed class or a subclass | passes, returns the exception | | raises nothing | Failure: `ArgumentError expected but nothing was raised.` | | raises an unrelated exception | Failure showing the class, message and backtrace it got instead | | fails an assertion inside the block | the assertion failure propagates unchanged | | raises `SystemExit` or a signal exception | re-raised, not swallowed | The fourth row matters: `Minitest::Assertion` is not rescued as "the expected error", so an assertion inside the block still reports its own failure. ## Checking the message and attributes The return value is the whole point. Typical checks on it: - `assert_equal "days late must be >= 0", error.message` for an exact message. - `assert_match(/must be >= 0/, error.message)` when only part of the message is stable. - Custom attributes of your own exception class, such as `error.days`, compared with `assert_equal`. A frequent mistake is passing the expected message as a second argument: `assert_raises(ArgumentError, "days late must be >= 0") { ... }`. That string is only the text printed **if the assertion fails**; it is never compared with the exception's message, so a wrong message still passes. Another trap is `assert_raises("ArgumentError") { ... }` with the class name as a string: the string is taken as the failure message, no class is left, and the assertion silently falls back to `StandardError`. ## Asserting that nothing is raised Minitest has no `refute_raises` and no equivalent of test-unit's `assert_nothing_raised`. The reasoning is that it adds nothing: if the block raises, Minitest already reports the test as an **Error** with the full exception and backtrace. So a "does not raise" test simply calls the code and, ideally, asserts on the result it returned: ```ruby def test_accepts_zero_days assert_equal 0, @calc.fine_for(0) end ``` ## The spec form In `Minitest::Spec` the same assertion is spelled `_ { @calc.fine_for(-1) }.must_raise ArgumentError`. It must receive a **block** (`_ { }`), not a value (`_( )`), because the value form evaluates the call before the expectation exists. It also returns the exception, so `.message` can be checked on the result. ## Reading an assert_raises failure The two failure messages point at different bugs. `ArgumentError expected but nothing was raised.` means the guard is missing or the input never reached it: check that the test passes the value you think it does. `[ArgumentError] exception expected, not` followed by another class means the code crashed differently, often before the guard, and the backtrace printed with it shows where. Both are Failures, because the assertion itself decided the outcome. ## Summary - Name the narrowest class you expect; broad classes pass on unrelated bugs. - Capture the return value and assert on `message` or attributes. - The trailing String is a failure message, not an expected message. - "Does not raise" needs no assertion: call the code.
- With Minitest, assert_raises(ArgumentError) wraps a block that raises KeyError. How is the test reported?As a Failure. `assert_raises` rescues any other `Exception` it did not expect and fails with a message saying `[ArgumentError] exception expected, not` followed by the `KeyError`'s class, message and backtrace. Only `Minitest::Assertion`, `SystemExit` and signal exceptions are re-raised unchanged.
- With Minitest, why is assert_raises(StandardError) a weak test for a custom validation error?Matching follows `rescue`, so any subclass passes, and almost every runtime bug (`NoMethodError`, `TypeError`, `ArgumentError`) is a `StandardError`. A typo inside `fine_for` would satisfy it. Name your specific error class and check the returned exception's message or attributes.
saying these in an interview costs you the question
- assert_raises(StandardError) fails when the block raises an ArgumentError.
- The second String argument to assert_raises is compared with the exception message.
- assert_raises returns true, so the message cannot be inspected.
- Use assert_nothing_raised to prove Minitest code does not raise.
- assert_raises works without a block if you pass the method call as an argument.