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
Companion reference for rust-unit-tests. A test double (per
ISTQB Glossary) replaces a real dependency so the subject
under test runs in isolation. mockall is the community-standard Rust
mocking crate; use it when a unit test reaches a database, HTTP client,
file system, or any trait boundary. For tests that do not cross a trait
boundary, prefer real objects or simple stubs.
Per docs.rs/mockall/latest/mockall:
[dev-dependencies]
mockall = "0.14.0"Two entry points: #[automock] for traits you own; mock! for structs or
traits defined in external crates.
Applying #[automock] generates a MockTraitName struct in the same
module:
use mockall::automock;
use mockall::predicate::*;
#[automock]
pub trait Cache {
fn get(&self, key: &str) -> Option<String>;
fn set(&mut self, key: &str, value: String);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lookup_hits_cache() {
let mut mock = MockCache::new();
mock.expect_get()
.with(eq("user:42"))
.times(1)
.returning(|_| Some("alice".to_string()));
let result = lookup(&mock, "user:42");
assert_eq!(result, Some("alice".to_string()));
}
}Expectations are verified automatically when the mock is dropped: a
declared times(1) with no call panics on drop and fails the test.
Use mock! when the trait lives in a dependency you cannot annotate:
use mockall::mock;
mock! {
pub HttpClient {}
impl reqwest_like::Client for HttpClient {
fn get(&self, url: &str) -> String;
fn post(&self, url: &str, body: &str) -> String;
}
}
#[test]
fn test_fetch_uses_get() {
let mut client = MockHttpClient::new();
client.expect_get()
.with(eq("https://api.example.com/v1/data"))
.times(1)
.return_once(|_| r#"{"ok":true}"#.to_string());
let result = fetch_data(&client);
assert!(result.contains("ok"));
}| Method | Behaviour |
|---|---|
.times(n) | Requires exactly n calls |
.times(..) | Any number (range syntax) |
.with(matcher) | Argument predicate from mockall::predicate::* |
.returning(closure) | Computes return value via FnMut |
.return_once(closure) | Consumes an FnOnce (for non-Clone returns) |
.return_const(value) | Clones and returns a constant |
.never() | Asserts the method is never called |
Common predicates (mockall::predicate::*): eq(v), ne(v), lt(v),
gt(v), function(fn), always(), never().
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Mock every dependency | Tests verify mock wiring, not behavior | Mock only true isolation boundaries |
Missing #[cfg(test)] on the mock module | Mock types compiled into the release binary | Wrap MockXxx usage in #[cfg(test)] |
mock! when #[automock] suffices | Verbose boilerplate for owned traits | #[automock] for traits in your crate |
#[automock] doesn't support some advanced generic trait patterns -
fall back to mock! with explicit type parameters.go-unit-tests
references/go-mocking.md