Claude
Skills
Sign in
Back

rust-testing

Included with Lifetime
$97 forever

Rust testing conventions and frameworks: built-in #[test], integration, and doctest layout plus the cargo-nextest runner and the proptest, insta, criterion, mockall, and rstest ecosystem. Invoke whenever task involves any interaction with Rust tests — writing, running, configuring, or debugging tests in .rs files and Cargo projects.

Backend & APIs

What this skill does


# Rust Testing

Prefer real implementations and hand-written fakes over heavy mocking — a test wired to a mock asserts how the code
calls its collaborators, not what the code does. Tests are documentation that runs: a reader should learn the intended
behavior from the test name, the arrange/act/assert shape, and the doctest examples on public items. Test the public
contract, not the private call graph.

## Route to Reference

- **Test layout depth** — `${CLAUDE_SKILL_DIR}/references/layout.md` Unit vs integration vs doctest mechanics, `tests/`
  crate-per-file model, `tests/common/mod.rs` shared-helper pattern, binary-crate `lib.rs` split, doctest attributes
  (`no_run`, `should_panic`, `ignore`, hidden `#` lines), `assert_matches!` and `pretty_assertions` usage
- **Property and snapshot testing, benchmarks** — `${CLAUDE_SKILL_DIR}/references/property-and-snapshot.md` proptest vs
  quickcheck strategy authoring and shrinking, insta inline/file snapshots and `cargo insta review`, criterion harness
  setup and `std::hint::black_box`
- **Mocking and fixtures** — `${CLAUDE_SKILL_DIR}/references/mocking-and-fixtures.md` mockall `#[automock]` vs `mock!`,
  predicates and call counts, the trait-seam pattern for in-memory fakes, rstest `#[case]`/`#[fixture]` params,
  serial_test, assert_cmd + predicates for CLI integration
- **Async tests** — `${CLAUDE_SKILL_DIR}/references/async-tests.md` `#[tokio::test]` flavors, `tokio::time::pause`/
  `advance` for deterministic timers, tokio-test macros; cross-references the `rust` skill async reference for runtime
  semantics
- **Pinned crate versions** — `${CLAUDE_SKILL_DIR}/references/versions.md` date-stamped dev-dependency versions for the
  testing ecosystem crates ("as of 2026-06")

Read the relevant reference before writing non-trivial tests in that area.

## Test Layout

- Place unit tests in a `#[cfg(test)] mod tests` block in the same file as the code under test; bring the parent into
  scope with `use super::*`. `#[cfg(test)]` keeps test code (and its helpers) out of the build artifact.
- Use unit tests to exercise private items — Rust's privacy rules let a child `tests` module reach its ancestors, so
  testing internals is possible. Reserve this for genuinely tricky private logic; prefer driving private code through
  the public API.
- Place integration tests in the top-level `tests/` directory, next to `src/`. Cargo compiles each file as its own
  crate, so they reach only the public API via `use my_crate::...` — exactly as an external consumer would.
- Share integration-test helpers from `tests/common/mod.rs`, not `tests/common.rs`. The `mod.rs` form is not compiled as
  a separate test crate, so it produces no spurious empty "running 0 tests" section. Import it with `mod common;`.
- For a binary crate, keep `main.rs` a thin shell that calls into `lib.rs`; integration tests then exercise the library
  crate, since only library crates expose items to external `use`.
- Write doctests as `///` examples on public items. They double as compiled documentation and run under
  `cargo test --doc`. Hide setup lines with a leading `#` and use ` ```no_run ` for examples that must compile but not
  run.

## Assertions

- Use `assert!` for booleans and `assert_eq!`/`assert_ne!` for value equality; add a trailing message argument when the
  failure is not self-explanatory.
- Use `assert_matches!` to assert a value matches a pattern (including enum variants with bindings) — it is stable in
  `std` as of Rust 1.96 and requires `use std::assert_matches::assert_matches;`.
- Add `pretty_assertions` as a dev-dependency and `use pretty_assertions::{assert_eq, assert_ne};` in test modules when
  comparing large structs or collections — it renders colored line-by-line diffs instead of one unreadable line.

## Runner

- Run unit and integration tests with `cargo nextest run` — it builds each test binary, queries it for tests, and runs
  every test in its own process in parallel, giving clean per-test isolation and output.
- Always pair it with `cargo test --doc`. Nextest does not run doctests, so a project that only runs nextest silently
  skips every doctest. Both commands belong in the verification gate.
- Scope runs with the same selectors as cargo: `-p <pkg>`/`--workspace`, `--lib`/`--test <name>`, `-E <filterset>` for
  expression filtering, and `--no-capture` to see test stdout/stderr.
- Drive flaky-test triage with `--retries <N>` and stop early with `--fail-fast`; prefer fixing nondeterminism over
  leaving retries on permanently.

## Mocking Decision Rule

Default to the cheapest test double that still proves the behavior, escalating only when the boundary forces it:

- **Real implementation** — call the actual code. The default for pure logic and anything with no external dependency.
- **In-memory fake** — a real working implementation backed by memory (e.g. a `HashMap`-backed store behind a repository
  trait). Preferred for stateful collaborators; the fake is reusable across many tests.
- **Hand-written double** — a small struct implementing the collaborator's trait with canned answers. Use when a fake is
  overkill and you need one or two stubbed responses.
- **mockall** — generate a mock with `#[automock]` (or `mock!` for foreign/multi-trait types) only at IO or
  nondeterministic boundaries: network, filesystem, clock, randomness, external services behind a trait seam.

Define collaborators behind a trait so the production type and the test double are interchangeable. Assert on observable
outcomes — return values, resulting state, emitted effects — not on which methods were called in which order. Do not use
mockall `Sequence`/`in_sequence` to lock internal call order; ordering assertions make tests break on harmless
refactors. Argument matchers and call counts are acceptable when the call itself is the contract (e.g. "charge the card
exactly once").

## Property, Snapshot, and Benchmarks

- Reach for property testing when a function must hold an invariant across a large input space (round-trips,
  idempotence, ordering). Use `proptest` (generates and shrinks values from declarative strategies) or `quickcheck`
  (derives generators from `Arbitrary`); proptest is the richer default for custom input domains.
- Use `insta` for snapshot testing of large or structural output (serialized data, rendered text, debug dumps). Review
  and accept changes with `cargo insta review`; commit the approved `.snap` files. Snapshots make intent diffable rather
  than hand-asserting every field.
- Put benchmarks in `benches/` with `criterion`, set `harness = false` on the `[[bench]]` target in Cargo.toml, and wrap
  benchmarked inputs/outputs in `std::hint::black_box` so the optimizer cannot fold the work away. Benchmarks measure;
  they are not correctness tests.

## Async Tests

- Annotate async tests with `#[tokio::test]`. Default to the current-thread flavor; use
  `#[tokio::test(flavor = "multi_thread", worker_threads = N)]` only when the test genuinely needs real parallelism.
- Make time deterministic with `tokio::time::pause()` and `tokio::time::advance(duration)` instead of real `sleep`.
  Advancing virtual time fires timers instantly and removes wall-clock flakiness from timeout and interval tests.
- Mock async-trait collaborators with mockall (compatible with `async_trait`/`trait_variant`), returning futures via
  `.returning(|| Box::pin(async { ... }))`. The mocking decision rule applies unchanged — mock only at IO boundaries.

## Application

When **writing** tests:

- Apply these conventions silently; do not narrate each rule. Name tests for the behavior under test, not the method.
- Match the surrounding project: if it already standardizes on `cargo test`, rstest fixtures, or a specific assertion
  crate, follow that and flag any divergence once rather than rewriting wholesale.
- Add the verification gate commands you rely on (`cargo nextest run`, `cargo test --doc`) when introducing tests to a
  project that lacks th

Related in Backend & APIs