Rust unit testing with the built-in `cargo test` harness - `#[test]` in `#[cfg(test)] mod tests` blocks, `assert_eq!` / `assert_ne!` / `assert!` macros, `#[should_panic(expected)]`, `Result<(), E>` test returns, integration tests in `tests/`, doc tests in `///` comments, runner flags (`--test-threads=1`, `--nocapture`, `--ignored`), `#[ignore]` marking, coverage via cargo-llvm-cov / tarpaulin, and Criterion benchmarks on stable. Includes framework choice (stdlib `#[test]` is the default; rstest for 4+ parameterized case pairs or shared fixtures via references) and test-authoring conventions (inline `#[cfg(test)]` placement, assertion-macro selection, async runtime requirements). References cover rstest parametrize + fixtures and Rust mocking with mockall (`#[automock]` / `mock!`). Use for any Rust unit-test task: writing tests, testing panics or Results, doc tests, coverage gates, benchmarks, or CI wiring.
72
91%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Per doc.rust-lang.org/book/ch11-00-testing.html:
Rust's testing is built into Cargo - the #[test] attribute marks test
functions; cargo test discovers and runs them. Three test categories per
the Rust Book:
| Category | Location | Purpose |
|---|---|---|
| Unit tests | Same file as code, in #[cfg(test)] mod tests { ... } | Test private + internal logic |
| Integration tests | tests/ directory at crate root | Test public API as an external user |
| Doc tests | Inside /// doc comments | Verify documentation examples |
#[test] is the default - built into the language, no
Cargo.toml change needed.#[rstest] + #[case] runs each pair as a named
test, discovered by cargo test natively →
references/rstest.md. Match an existing rstest
convention (rstest in [dev-dependencies] AND #[rstest] usage in
tests) rather than introducing it ad hoc.proptest-testing (qa-property-based
plugin).// src/math.rs
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn adds_two_numbers() {
assert_eq!(add(1, 2), 3);
}
}cargo test # all tests
cargo test add # filter by name pattern
cargo test --lib # only unit tests in lib
cargo test --all-targets # everything
cargo test --workspace # multi-crate workspace#[cfg(test)] keeps the module out of release builds.
assert!(condition, "format message: {}", value);
assert_eq!(actual, expected);
assert_ne!(actual, unexpected);assert_eq! / assert_ne! print BOTH left and right on failure; bare
assert!(x == y) only reports false (rust-test). For
diff-rich struct comparisons, the pretty_assertions crate colorizes the
output.
#[should_panic] and Result returns#[test]
#[should_panic(expected = "negative")]
fn specific_panic_message() {
sqrt(-1.0);
}
#[test]
fn parses_config() -> Result<(), Box<dyn Error>> {
let cfg = Config::from_file("test/fixtures/config.toml")?;
assert_eq!(cfg.port, 8080);
Ok(())
}The Result return allows ? in test bodies - a failing ? fails the
test with the real error instead of "called unwrap on None".
my-crate/
src/lib.rs
tests/
integration_test.rs # automatically discovered
common/mod.rs # shared helpers (NO mod.rs in tests/ root)Each file in tests/ compiles to its own binary - slower but
better-isolated; only the crate's public API is visible.
/// Adds two numbers.
///
/// # Examples
///
/// ```
/// use my_crate::math::add;
/// assert_eq!(add(1, 2), 3);
/// ```
pub fn add(a: i32, b: i32) -> i32 { a + b }cargo test --doc runs only doc tests; cargo test runs everything. The
example IS the test, so docs can't drift from the implementation.
#[ignore]cargo test -- --test-threads=1 # serial
cargo test -- --nocapture # show println! output
cargo test -- --ignored # only #[ignore]-marked tests
cargo test -- --include-ignored # ignored + normal
cargo test some_pattern -- --exact # exact name match#[test]
#[ignore = "Requires network access"]
fn integration_with_external_api() { ... }Always include the = "reason" or ignored tests get forgotten.
Coverage needs an extra crate - cargo-llvm-cov (cross-platform,
recommended) or cargo-tarpaulin (Linux-only):
cargo install cargo-llvm-cov
cargo llvm-cov --html
cargo llvm-cov --lcov --output-path coverage.lcov
cargo llvm-cov --fail-under-lines 80 # gate at 80%Benchmarks on stable use Criterion (stdlib #[bench] is nightly-only and
breaks CI):
[dev-dependencies]
criterion = "0.5"
[[bench]]
name = "math_bench"
harness = false// benches/math_bench.rs
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use my_crate::math::add;
fn bench_add(c: &mut Criterion) {
c.bench_function("add 1 2", |b| b.iter(|| add(black_box(1), black_box(2))));
}
criterion_group!(benches, bench_add);
criterion_main!(benches);Run cargo bench (bheisler.github.io/criterion.rs).
- run: cargo test --all-targets --workspace
- run: cargo test --doc
- run: cargo install cargo-llvm-cov
- run: cargo llvm-cov --lcov --output-path coverage.lcov
- uses: codecov/codecov-action@v4
with: { files: coverage.lcov }When authoring a new unit test in an existing project:
#[test] unless rstest is in
[dev-dependencies] AND existing tests use #[rstest]. Conflicting
signals → stop and ask.#[cfg(test)] mod tests block at the end of the source file; use a
separate tests/<name>.rs only for public-API integration scenarios
(doc.rust-lang.org/cargo/guide/tests).assert!(true) smoke asserts when the spec names a concrete value.#[test] fn calling .await does
not compile - use #[tokio::test] (when the project depends on Tokio)
or rstest's #[future] injection
(references/rstest.md).proptest-testing (qa-property-based).| Anti-pattern | Why it fails | Fix |
|---|---|---|
Skip --all-targets | Doc tests + benches + examples not run | Always --all-targets (Step 1) |
unwrap() in test bodies | Failure message is "called unwrap on None" | Result<(), E> return + ? (Step 3) |
#[ignore] without a reason | Forgotten ignored tests | = "reason" (Step 6) |
assert!(x == y) | Loses the value diff on failure | assert_eq! (Step 2) |
Nightly #[bench] in CI | Requires nightly toolchain | Criterion on stable (Step 7) |
mod tests shared state (rstest adds
fixtures - references/rstest.md).cargo test referencego-unit-tests - sister umbrella for Goproptest-testing (qa-property-based) - Rust property-basedtest-code-conventions (qa-test-review) - test code hygiene