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
- Missing
Debug:assert_eq!on a struct without it iserror[E0277]: `Order` doesn't implement `Debug`. MissingPartialEqgiveserror[E0369]: binary operation `==` cannot be applied to type `Order`. - Only checking the happy path. A test with a typical input catches typos; bugs live in the unusual inputs.
assert!(a == b)instead ofassert_eq!(a, b): it works, but a failure only says the condition was false, without showing the two values.
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.