---
title: "14.2 Writing tests"
description: "The #[test] attribute, the assert! family, assert_eq! and assert_ne!, testing for panics with #[should_panic], and tests that return Result."
url: "https://learnrust.net/chapter-14/writing-tests/"
last_updated: "2026-06-13"
---

# 14.2 Writing tests

Time to write a test and watch it pass. A Rust test is just a function with an attribute on it; `cargo test` finds every such function, runs it, and reports the results. This lesson covers the mechanics: the `#[test]` attribute, the assertion macros that make a test check something, and the two special forms for testing panics and `Result`s.

## The #[test] attribute

A test is a function marked with the `#[test]` attribute (the `#[...]` syntax from lesson [10.7](https://learnrust.net/chapter-10/deriving-debug/)). Here's a function and a test for it in the same file:

```rust
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

#[test]
fn adds_two_numbers() {
    let result = add(2, 3);
    assert_eq!(result, 5);
}
```

Run `cargo test` and the framework finds `adds_two_numbers`, runs it, and checks the assertion:

```
running 1 test
test adds_two_numbers ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
```

The function takes no arguments and returns nothing (usually). `#[test]` is what marks it as a test rather than ordinary code, so a normal `cargo build` ignores it and only `cargo test` compiles and runs it. A test *passes* if it runs to the end without panicking, and *fails* if anything in it panics, which is why the assertion macros work by panicking on failure.

## The assert! family

Three macros do almost all the checking. `assert!(condition)` passes if the condition is `true` and panics if it's `false`:

```rust
#[test]
fn five_is_positive() {
    assert!(5 > 0);
}
```

`assert_eq!(a, b)` passes if `a == b`, and on failure panics *and prints both values*, which is far more useful than a bare `assert!(a == b)` that would only tell you "false." `assert_ne!(a, b)` is the opposite, passing when the two differ:

```rust
#[test]
fn arithmetic_works() {
    assert_eq!(add(2, 2), 4);
    assert_ne!(add(2, 2), 5);
}
```

When `assert_eq!` fails, the output shows what it got versus what it expected:

```
running 1 test
test arithmetic_works ... FAILED

failures:

---- arithmetic_works stdout ----
thread 'arithmetic_works' panicked at src/lib.rs:4:5:
assertion `left == right` failed
  left: 4
 right: 5
```

That `left: 4, right: 5` is why you reach for `assert_eq!` over `assert!`: it tells you not just *that* the check failed but *how*, which is most of the way to the fix. (`assert_eq!` requires the values to be comparable with `==` and printable for the failure message; for your own types that means deriving `PartialEq` and `Debug`, the derives from lesson [10.7](https://learnrust.net/chapter-10/deriving-debug/), with the full story in chapter 16.)

> **Best practice**
>
> Prefer `assert_eq!`/`assert_ne!` over `assert!(a == b)` whenever you're comparing values, so failures show both sides. Reach for plain `assert!` only when the check is a genuine boolean that isn't a comparison (`assert!(list.is_empty())`, `assert!(result.is_ok())`). You can add a custom message to any of them: `assert!(x > 0, "x was {x}, expected positive")`.

## Testing that something panics

Sometimes the *correct* behavior is a panic: a function that's supposed to reject bad input by panicking (lesson [12.1](https://learnrust.net/chapter-12/panic-and-unrecoverable-errors/)). You test that with `#[should_panic]`, which inverts the pass condition, so the test passes only if the body *does* panic:

```rust
pub fn checked_age(age: i32) -> i32 {
    if age < 0 {
        panic!("age cannot be negative");
    }
    age
}

#[test]
#[should_panic]
fn rejects_negative_age() {
    checked_age(-5);
}
```

`rejects_negative_age` passes because `checked_age(-5)` panics as intended. You can be stricter and require the panic message to contain specific text, which guards against the test passing for the *wrong* panic: `#[should_panic(expected = "cannot be negative")]`. Without `expected`, any panic counts, which can mask a different bug.

## Tests that return Result

A test can return `Result<(), E>` instead of nothing, which lets you use the `?` operator (lesson [12.3](https://learnrust.net/chapter-12/propagating-errors-question-mark-operator/)) inside it. The test passes on `Ok(())` and fails on `Err`:

```rust
#[test]
fn parses_a_number() -> Result<(), std::num::ParseIntError> {
    let n: i32 = "42".parse()?;
    assert_eq!(n, 42);
    Ok(())
}
```

This is handy when a test does several fallible steps: instead of unwrapping each, you `?` them, and any error fails the test with the error attached. Use the plain (non-`Result`) form for ordinary assertions and the `Result` form when `?` would save you a pile of `unwrap`s. Note that with a `Result`-returning test you can't use `#[should_panic]`; to check for an `Err`, assert on it directly (`assert!(result.is_err())`).

## Quiz time

**Question #1**

What attribute marks a function as a test, when does a test pass, and when does it fail?

<details class="solution">
<summary>Show solution</summary>

`#[test]` marks a test function. It passes if it runs to completion without panicking, and fails if anything in it panics, which is how the assertion macros report failure (they panic when the check fails).

</details>

**Question #2**

Why is `assert_eq!(a, b)` usually better than `assert!(a == b)`?

<details class="solution">
<summary>Show solution</summary>

On failure, `assert_eq!` prints both values (`left: 4, right: 5`), telling you not just that the check failed but what the actual and expected values were. `assert!(a == b)` only reports that a boolean was false, giving you no information about the values. The extra detail from `assert_eq!` is most of the way to diagnosing the failure.

</details>

**Question #3**

Write a test verifying that a function `sqrt_checked(n: i32)` panics when given a negative number, and require the panic message to mention "negative".

<details class="solution">
<summary>Show solution</summary>

```rust
#[test]
#[should_panic(expected = "negative")]
fn rejects_negative() {
    sqrt_checked(-1);
}
```

`#[should_panic(expected = "negative")]` makes the test pass only if the body panics *and* the panic message contains "negative", guarding against the test passing for an unrelated panic.

</details>

Your tests so far have sat loose in the file. The next lesson covers the conventional way to organize unit tests, the `#[cfg(test)] mod tests` block, and tackles the question of whether (and how) to test private functions.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
