Working Code
Tests live right next to your library code for the fastest feedback loop. Place #[test] functions inside a #[cfg(test)] module — they won't be included in release builds.
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
pub fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
return Err("Cannot divide by zero".into());
}
Ok(a / b)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn add_works() {
assert_eq!(add(2, 3), 5);
assert_ne!(add(-1, 1), 0);
}
#[test]
fn divide_returns_error() -> Result<(), String> {
assert_eq!(divide(10, 2)?, 5);
assert!(divide(5, 0).is_err());
Ok(())
}
}
assert_eq!,assert_ne!, andassert!show you exactly which values differed on failure.- When a test function returns
Result<(), E>, you can use the?operator for cleaner error handling.
Exploring cargo test
Run cargo test in the terminal and it automatically finds and runs all tests:
$ cargo test
Compiling mylib v0.1.0 (/Users/dale/mylib)
Finished test [unoptimized + debuginfo] target(s) in 1.15s
Running unittests src/lib.rs (target/debug/deps/mylib-abc123)
test tests::add_works ... ok
test tests::divide_returns_error ... ok
failures:
tests::add_works
---- tests::add_works stdout ----
thread 'tests::add_works' panicked at 'assertion failed: `(left == right)`
left: `6`,
right: `5`', src/lib.rs:8:9
ok/FAILEDlines give you a quick overview.- Failed tests show a stack trace and a summary at the end for easy debugging.
- Run a specific test with
cargo test add_works.
Integration Test Structure
To verify behavior from outside the library, use the tests/ directory. Each file there is compiled as an independent binary, testing real usage scenarios:
my-project/
├─ src/
│ └─ lib.rs
├─ tests/
│ ├─ cli.rs
│ └─ api.rs
└─ Cargo.toml
Example tests/cli.rs:
use assert_cmd::Command;
#[test]
fn prints_help() {
let mut cmd = Command::cargo_bin("todo")
.expect("binary todo not found");
cmd.arg("--help").assert().success();
}
- Integration tests can only access public APIs. Test private internals with unit tests.
- If you need shared helpers, put them in
tests/common/mod.rsand import withmod common;from each test file.
Try It Yourself
- Create a
fn greet(name: &str) -> Stringinsrc/lib.rsand write a test that verifies it returns "Hello, {name}". - Intentionally write a failing test and observe how the
cargo testoutput differs. - Create a
tests/folder and write a short integration test to verify the library works as expected end-to-end.
- Use assert! instead of panic! — Rewrite the code below to use
assert!/assert_eq!with descriptive messages instead of manualpanic!calls. - Result-returning tests — Assume a function
fn parse_user_id(input: &str) -> Result<u32, String>. Write two tests: one for success and one for failure. - Integration test — Assuming you have a CLI program, write a test in
tests/cli.rsthat runs the binary withCommand::cargo_binand checks stdout.
Q1. How does Rust identify test functions?
- A) The function name must start with
test_ - B) The function must have the
#[test]attribute - C) It must be inside a
testsmodule - D) It must use the
cfg(test)macro
Q2. What's the advantage of tests that return Result<(), E>?
- A) Tests run faster
- B) You can use the
?operator for concise error propagation - C) You can't use assert macros
- D) cargo test hides error messages
Q3. Which statement about integration tests (tests/ folder) is correct?
- A) They can access private functions
- B) Each file is built as an independent binary
- C) They only run with
cargo test --lib - D)
usestatements can't be used in integration tests