RustArena

Step 2 of 6

assert_eq!, messages, and comparing your own types

When a test fails, the first thing you read is its message. Good messages save real time.

What a failure looks like

assert_eq! and assert_ne! print both values when they fail. By convention the first argument is the value your code produced and the second is the one you expected, though Rust only calls them left and right. Any extra arguments after the two values are a custom message, written like a format! string:

fn add_vat(cents: u32) -> u32 {
    cents + cents / 5
}

fn main() {
    assert_eq!(add_vat(100), 120);
    println!("100 is fine");
    let price = 7;
    assert_eq!(add_vat(price), 9, "add_vat({price}) should round up");
    println!("7 is fine");
}
100 is fine

The second assert fails (7 / 5 is 1, so the result is 8) and the program stops with:

thread 'main' (4242) panicked at src/main.rs:9:5:
assertion `left == right` failed: add_vat(7) should round up
  left: 8
 right: 9

The message names the input, so you know which case failed without opening the test. That matters most in tests that check many inputs.

Comparing your own types

assert_eq! needs two things from the values: PartialEq (to compare them with ==) and Debug (to print them when they differ). For your own structs and enums, derive both:

#[derive(Debug, PartialEq)]
struct Order {
    qty: u32,
    item: String,
}

fn parse_order(line: &str) -> Option<Order> {
    let (qty, item) = line.split_once(" x ")?;
    let qty: u32 = qty.trim().parse().ok()?;
    Some(Order { qty, item: item.trim().to_string() })
}

fn main() {
    assert_eq!(parse_order("3 x apple"), Some(Order { qty: 3, item: String::from("apple") }));
    assert_eq!(parse_order("lots of apples"), None);
    println!("{:?}", parse_order("2 x pear"));
}
Some(Order { qty: 2, item: "pear" })

Comparing the whole Option<Order> in one assert checks every field at once, and if it fails you see both complete values.

Floating point

0.1 + 0.2 == 0.3 is false with floats (try it), so don't assert_eq! computed f64s. Check they're close instead: assert!((actual - expected).abs() < 1e-9, "got {actual}").

Common mistakes

Your task

apply_discount takes an order total in cents and a discount code: SAVE10 takes 10% off, HALF halves the total (rounding down, so an odd number of cents loses the half cent), and any other code changes nothing. Write at least 3 tests, and give at least one assert_eq! a custom message. Your tests must catch every planted bug.

Challenge 2

parse_order turns a line like 3 x apple into an Order. Quantities must be whole numbers of at least 1, and spaces around the item name are ignored. Anything else gives None. Write at least 3 tests that compare whole Option<Order> values with assert_eq!.

Challenge 3

to_fahrenheit converts Celsius to Fahrenheit (F = C * 9 / 5 + 32). Float results shouldn't be compared with ==, so write a helper fn close(a: f64, b: f64) -> bool (true when they differ by less than 1e-9) and use it in at least 2 tests with assert!, including a custom message that shows the actual value.

Previous: Your first test Next: Tests that catch bugs