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
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