Rust Testing
High-signal guidance for Rust tests that are deterministic, maintainable, and worth running.
Use this skill when tests are part of the change, when flakiness needs to die quietly, or when CI needs stronger signal.
When to activate
- Adding unit, integration, async, or doctests for Rust code
- Refactoring brittle or slow Rust tests
- Introducing fakes, mocks, property tests, or snapshot tests
- Tightening CI feedback with coverage, nextest, or command selection
- Hardening parsers,
unsafe, or FFI code against arbitrary input with fuzzing, Miri, or sanitizers - Testing locks, atomics, or shutdown joins (Loom, Miri races, deadlock)
Core rules
- Prefer small unit tests for logic and integration tests for public behavior and boundaries.
- Keep tests deterministic: avoid sleep-based timing and shared mutable global state.
- Use mocks sparingly; prefer fakes, temp dirs, test servers, and controlled inputs.
- Treat doctests as part of the public contract, not decorative comments.
- Coverage is a signal, not the goal; strong assertions beat inflated percentages.
- Avoid production-only test hooks unless they expose a legitimate public or crate-private seam.
- Pair with
test-driven-developmentwhen implementing persistent code or bug fixes test-first. - Pair with
testing-reliabilityfor mock/timing anti-patterns andsystematic-debuggingwhen the root cause is unclear.
Outcome expectations
- Tests are deterministic, debuggable, and fast enough to run frequently.
- Boundaries (time/fs/network/randomness) are explicit and controllable.
- CI separates fast confidence checks from heavier coverage/property/snapshot stages.
Recommended workflow
- Start with focused unit/integration tests for the behavior change.
- Run targeted commands before full-suite reruns.
- Stabilize async/boundary tests via explicit timeouts and local fixtures.
- Add property/snapshot tests when they increase signal, not just volume.
- Verify CI command mix remains fast and reproducible.
Quick review checklist
- Test names describe behavior, not implementation trivia
- Setup is short and local; helpers remove noise without hiding intent
- External boundaries (time, filesystem, network, randomness) are controlled explicitly
- Async tests await real conditions instead of sleeping and hoping
- CI runs the right mix of
cargo test, doctests, and any nextest/coverage steps
Resources
Load on demand:
references/unit-and-integration.md— use when deciding what belongs beside the code vs undertests/, when pickingrstestover table loops,should_panicdiscipline, ortrybuildcompile-fail testsreferences/async-and-boundaries.md— use when testing async code, time, IO, and network boundariesreferences/property-snapshot-and-mocks.md— use when example-based tests are not enough, when output is bulky, or when scriptingmockallexpectationsreferences/fuzzing-and-sanitizers.md— use to hunt panics, memory-safety bugs, and UB on arbitrary input (cargo-fuzz, arbitrary, AFL, Miri, sanitizers)references/concurrency-testing.md— load when testing atomics/Mutex/channels: Loom models, Miri races vs TSan, deadlock/OnceLockre-entryreferences/coverage-and-ci.md— use when wiring coverage, nextest profiles, mutation testing, or stable CI gatesreferences/commands.md— use for the most common Rust test commands and selectors