#[should_panic]
The #[should_panic] attribute inverts the pass/fail condition of a #[test] function: the test passes only if the function panics during execution, and fails if the function returns normally without panicking.
#[test]
#[should_panic(expected = "substring")]
fn my_test()
|
+-- Function returns normally --> TEST FAILS ("note: test did not panic as expected")
+-- Panics with wrong message --> TEST FAILS (shows expected vs actual panic message)
+-- Panics matching substring --> TEST PASSES ("ok")
Bare #[should_panic] vs #[should_panic(expected = "...")]
A bare #[should_panic] attribute passes if any panic occurs anywhere inside the test function—even if the panic happened in an unrelated setup line before the function under test was called.
Adding expected = "..." restricts the test so that it only passes if the panic message contains the specified substring. Always prefer #[should_panic(expected = "...")] so unrelated bugs in test setup cannot masquerade as a passing test.
| Attribute Form | Passes When | Risk |
|---|---|---|
#[should_panic] |
Any panic occurs in the test body | High: an accidental unwrap() failure in setup passes the test |
#[should_panic(expected = "msg")] |
Panic message contains "msg" as a substring |
Low: verifies the specific failure reason |
pub struct Percentage(u8);
impl Percentage {
pub fn new(value: u8) -> Self {
assert!(
value <= 100,
"Percentage out of range: expected 0..=100, got {value}"
);
Self(value)
}
pub fn value(&self) -> u8 {
self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic(expected = "Percentage out of range: expected 0..=100, got 125")]
fn rejects_values_above_100() {
let _ = Percentage::new(125);
}
}
fn main() {
let valid = Percentage::new(85);
println!("Valid percentage: {}%", valid.value());
}
Valid percentage: 85%
Running cargo test verifies that rejects_values_above_100 panics with the expected substring:
running 1 test
test tests::rejects_values_above_100 ... ok
test result: ok. 1 passed; 0 failed; 0 filtered out; finished in 0.00s
Diagnostic Output When expected Does Not Match
If a #[should_panic(expected = "...")] test panics with a message that does not contain the expected substring, cargo test fails the test and prints both strings:
---- tests::rejects_values_above_100 stdout ----
thread 'tests::rejects_values_above_100' panicked at src/lib.rs:6:9:
Percentage out of range: expected 0..=100, got 101
note: panic did not contain expected string
panic message: `"Percentage out of range: expected 0..=100, got 101"`,
expected substring: `"got 125"`
Incompatible with Result Return Types
A #[should_panic] test must return (). Combining #[should_panic] with fn test() -> Result<(), E> is rejected at compile time:
// ❌ does not compile
#[test]
#[should_panic]
fn cannot_return_result() -> Result<(), String> {
panic!("boom");
}
error: functions using `#[should_panic]` must return `()`
--> src/lib.rs:4:1
|
4 | fn cannot_return_result() -> Result<(), String> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^