Can you write this from memory?
Write the attribute that marks a function as a test.
Rust has first-class testing support built into the language and tooling. #[test] marks test functions. assert! macros check conditions. cargo test runs everything.
Unit tests live alongside code. Integration tests live in a tests/ directory. Doc tests run code examples in documentation.
This page focuses on test syntax, organization, and the cargo test flags you'll use constantly.
| Command | Does |
|---|---|
cargo test | Run all tests (unit + integration + doc) |
cargo test test_name | Run tests matching "test_name" |
cargo test -- --no-capture | Show println! output |
cargo test -- --ignored | Run only #[ignore] tests |
cargo test -- --include-ignored | Run all tests including ignored |
cargo test --test integration_test | Run a specific integration test file |
cargo test --doc | Run doc tests only |
cargo test --lib | Run unit tests only |
The -- separates cargo flags from test runner flags. Everything after -- goes to the test binary.
The test module is a child scope — it cannot see the parent's items without importing them:
fn add(a: i32, b: i32) -> i32 { a + b }
// WRONG: test can't find add()
// #[cfg(test)]
// mod tests {
// #[test]
// fn test_add() {
// assert_eq!(add(2, 2), 4); // error[E0425]: cannot find function
// }
// }
// RIGHT: import from parent module
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 2), 4);
}
}
use super::*; imports everything from the parent module. Add it as the first line inside mod tests.
Integration tests in tests/ are external crates — they can only see your public API:
// src/lib.rs
fn internal_helper() -> i32 { 42 } // private
pub fn public_api() -> i32 { internal_helper() }
// tests/integration_test.rs
// WRONG: private items are not visible
// use my_crate::internal_helper; // error[E0603]: function is private
// RIGHT: test the public API
use my_crate::public_api;
#[test]
fn test_api() {
assert_eq!(public_api(), 42);
}
If you need to test internal logic, use a unit test inside the source file instead. Visibility rules determine what integration tests can access -- see modules and crates for details on pub, pub(crate), and module structure.
#[test] functions must return a type that implements Termination: either () or Result<(), E> where E: Debug:
// WRONG: i32 doesn't implement Termination
// #[test]
// fn test_parse() -> i32 {
// "42".parse().unwrap()
// }
// RIGHT: return Result to use ? in tests
#[test]
fn test_parse() -> Result<(), Box<dyn std::error::Error>> {
let n: i32 = "42".parse()?;
assert_eq!(n, 42);
Ok(())
}
Returning Result lets you use ? instead of .unwrap() chains, and the error message on failure is more informative. The error handling guide covers the ? operator and Result combinators in depth.
Unit Tests
Go inside the source file in a #[cfg(test)] mod tests block. Can test private functions:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
#[cfg(test)] means this code is only compiled when running tests — it stays out of production builds. The error handling cheat sheet is a useful companion when writing assertions and test helpers.
Integration Tests
Live in the tests/ directory at the crate root. Each file is compiled as a separate crate:
// tests/integration_test.rs
use my_crate::public_function;
#[test]
fn test_public_api() {
assert!(public_function());
}
Sharing Code Between Integration Tests
Each file in tests/ is a separate crate, so sharing helpers requires a module:
// tests/common/mod.rs (must use mod.rs style, not common.rs)
pub fn setup() -> TestContext {
// shared setup code
}
// tests/integration_test.rs
mod common;
#[test]
fn test_with_setup() {
let ctx = common::setup();
// ...
}
The Binary Crate Gotcha
Integration tests import your crate with use my_crate::..., which only works for library crates (src/lib.rs). If your project is binary-only (src/main.rs), move testable logic into src/lib.rs and have main.rs call into it.
Doc Tests
Code examples in doc comments run with cargo test:
/// Adds two numbers.
///
/// ```
/// let result = my_crate::add(2, 3);
/// assert_eq!(result, 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 { a + b }
Doctest attributes control compilation and execution:
| Attribute | Compiles? | Runs? | Use for |
|---|---|---|---|
| (none) | Yes | Yes | Normal examples |
no_run | Yes | No | Examples that need network/files |
ignore | No | No | Incomplete or broken examples |
should_panic | Yes | Yes (expects panic) | Demonstrating panics |
compile_fail | Yes (expects error) | No | Showing what doesn't compile |
Testing panics — expected does a substring match on the panic message:
#[test]
#[should_panic(expected = "divide by zero")]
fn test_panic() {
divide(1, 0);
}
Ignored tests with reason:
#[test]
#[ignore = "requires database connection"]
fn expensive_test() {
// run with: cargo test -- --ignored
}
Custom assertion messages — add context to failures:
#[test]
fn test_bounds() {
let val = compute();
assert!(val > 0, "expected positive value, got {val}");
assert_eq!(val, 42, "compute() should return 42, got {val}");
}
- The Rust Book: Testing — unit, integration, and doc tests
- The Rust Book: Test Organization — cfg(test), tests/ directory
- Rust By Example: Testing — practical test patterns
- Rustdoc: Documentation Tests — doctest attributes and syntax
When to Use Rust Testing: #[test], assert!, Unit & Integration Tests
- Write unit tests for individual functions and modules.
- Write integration tests for public API behavior.
- Use #[should_panic] for testing error conditions.
- Use #[ignore] for slow tests run separately.
- Use doc tests to keep examples in sync with code.
Check Your Understanding: Rust Testing: #[test], assert!, Unit & Integration Tests
How do you organize tests in a Rust project?
Unit tests go in a `#[cfg(test)] mod tests` inside each source file—they can test private functions. Integration tests go in the `tests/` directory and only test public APIs. Doc tests are code examples in documentation comments.
What You'll Practice: Rust Testing: #[test], assert!, Unit & Integration Tests
Common Rust Testing: #[test], assert!, Unit & Integration Tests Pitfalls
- Test says "cannot find function" -- your test module is missing `use super::*;`. Without it, nothing from the parent module is in scope. Add `use super::*;` as the first line inside `mod tests`.
- All your assertions pass but the test still fails -- you returned something other than `()` or `Result<(), E>` where `E: Debug`. Change the return type or return nothing.
- Test failure says "left: 5, right: 5" with no context -- you used `assert!(a == b)` instead of `assert_eq!(a, b)`. `assert_eq!` prints both values and accepts a custom message.
- Integration test gets "function is private" (E0603) -- tests in `tests/` only see your public API. Either make the item public or write a unit test inside the source file.
- Tests pass but println! output is invisible -- `cargo test` captures stdout by default. Use `cargo test -- --no-capture` to see output.
- `#[ignore]` tests never run in CI -- ignored tests need `cargo test -- --ignored` or `--include-ignored`. Add this to your CI config explicitly.
Rust Testing: #[test], assert!, Unit & Integration Tests FAQ
What is the difference between assert! and assert_eq!?
`assert!(condition)` checks a boolean. `assert_eq!(a, b)` checks equality and prints both values on failure, making debugging easier. Use `assert_eq!` when comparing values.
How do I test private functions?
Unit tests in the same file (inside `#[cfg(test)] mod tests`) can access private functions via `use super::*`. Integration tests cannot—they only see public APIs.
How do I run a specific test?
`cargo test test_name` runs tests containing "test_name" in their path. `cargo test --test integration_test` runs a specific integration test file.
What is #[cfg(test)]?
Conditional compilation. Code inside `#[cfg(test)]` is only compiled when running tests. This keeps test code out of production builds.
Can tests return Result?
Yes. Tests can return `Result<(), E>` where E: Debug. The test passes if Ok, fails if Err. This lets you use `?` for cleaner error handling instead of `.unwrap()` chains.
Why doesn't println! show in test output?
`cargo test` captures stdout by default. Use `cargo test -- --no-capture` to see printed output. Only failing tests show their output by default.
What are doctest attributes like no_run and compile_fail?
`no_run` compiles but skips execution (for examples needing I/O). `compile_fail` expects a compile error (for showing invalid code). `ignore` skips entirely. `should_panic` expects a runtime panic.
Rust Testing: #[test], assert!, Unit & Integration Tests Syntax Quick Reference
#[test]
fn it_works() {
assert!(true);
}assert_eq!(add(2, 2), 4);assert_ne!(result, 0);assert!(x > 0, "x must be positive, got {}", x);#[test]
#[should_panic]
fn test_panic() {
panic!("boom");
}#[should_panic(expected = "error")]#[test]
#[ignore]
fn slow_test() {
assert_eq!(2 + 2, 4);
}#[cfg(test)]
mod tests { use super::*; }#[test]
fn test() -> Result<(), Box<dyn std::error::Error>> {
let n: i32 = "42".parse()?;
assert_eq!(n, 42);
Ok(())
}#[test]
#[ignore = "needs database"]
fn slow_test() {}Rust Testing: #[test], assert!, Unit & Integration Tests Sample Exercises
Add the attribute to mark this function as a test that will run with `cargo test`.
#[test]Use the assertion macro that checks two values are equal.
assert_eq!Use the assertion macro that checks a boolean condition is true.
assert!+ 22 more exercises