Pebellslearn by building
0%
RoadmapsMatrixCoursesSandboxSign in
Testing  ›  Lesson

Unit Tests with #[test]

Unit & Integration Testing 15 minUnit tests live in a #[cfg(test)] module beside the code they check, each #[test] function passes unless it panics, and cargo test runs them all.

Unit Tests with #[test]

The late-fee rule changed: 10 per day, but never more than 200. You edit one line. Did you break the zero-days case? Rust has testing built into the language and into Cargo, so checking takes one command and no extra setup.

The rule

Tests sit beside the code; a test passes unless it panics.

// src/fees.rs
pub fn late_fee(days_late: u32) -> u32 {
    (days_late * 10).min(200)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn charges_ten_per_day() {
        assert_eq!(late_fee(3), 30);
    }

    #[test]
    fn nothing_when_on_time() {
        assert_eq!(late_fee(0), 0);
    }

    #[test]
    fn capped_at_two_hundred() {
        assert_eq!(late_fee(50), 200);
    }
}
  • #[cfg(test)] compiles the module only for cargo test, so it adds nothing to real builds.
  • use super::*; brings the parent module's items in. Because tests is a child module, it can test private functions too.
  • #[test] marks a function as a test. It passes if it returns normally and fails if it panics.

The assert macros panic with a helpful message when a check fails:

  • assert!(cond): the condition must be true.
  • assert_eq!(left, right) and assert_ne!(left, right): on failure they print both values.

Expected panics and Result. Suppose lend panics with the message "already on loan" when a book is already out:

#[test]
#[should_panic(expected = "already on loan")]
fn cannot_lend_twice() {
    let mut book = Book::new("Iron Tides", 380);
    book.lend();
    book.lend();
}

#[test]
fn parses_card() -> Result<(), std::num::ParseIntError> {
    assert_eq!("1042".parse::<u32>()?, 1042);
    Ok(())
}

Running them: cargo test runs every test (in parallel). cargo test capped runs only tests whose names contain "capped". Output printed by passing tests is hidden unless you add -- --nocapture.

The shortcut

For every function, write three tests: the ordinary case, the edge, and the limit.

Three tests for late_feeStep 1 of 4
  1. Ordinary

    3 days late → 30. assert_eq!(late_fee(3), 30);

The trap is a test with no assertion. It calls the function and passes as long as nothing panics, which proves almost nothing.

Drills

Answer fast — trust the rule.

quick drill

When does a #[test] function fail?

quick drill

What does #[cfg(test)] on the tests module do?

quick drill

Can a unit test in mod tests call a private function from the same file?

quick drill

Which command runs only tests with "capped" in their name?

Remember

  • Unit tests go in #[cfg(test)] mod tests beside the code; a test passes unless it panics.
  • Test the ordinary case, the edge and the limit, with assert_eq! for clear failures.
  • Trap: a test with no assertion proves nothing.

Practice — level up

0/1 solved

Write your answer, then compare it with a rubric and a model answer.

ExplainTest the late fee

The library has fn late_fee(days_late: u32) -> u32 that charges 10 per day, capped at 200. Describe the unit tests you would write, where they go in the file, and how one test checks that a separate function panics on bad input.