Rust Testing Patterns
Unit tests, integration tests, doctests, proptest, and CI patterns.
# CLAUDE.md — Rust Testing Patterns
## Test layout
- **Unit tests** live in the same file as the code, in a `#[cfg(test)] mod tests { ... }` block.
- **Integration tests** live in `tests/` at the crate root. Each file is a separate binary.
- **Doc tests** live in doc comments, run by `cargo test`.
- **Benchmarks** in `benches/` with **criterion** for stable measurements.
## Running
- `cargo test` runs everything. `cargo test --lib` for unit tests only.
- `cargo test <pattern>` filters by name. Useful when iterating.
- `cargo nextest run` is faster than `cargo test` for medium-to-large suites. Worth installing.
## Assertions
- `assert!`, `assert_eq!`, `assert_ne!` from std are enough most of the time.
- For richer output, **pretty_assertions** crate — `pretty_assertions::assert_eq!` shows colored diffs.
- For complex shapes, **insta** for snapshot tests:
```rust
insta::assert_yaml_snapshot!(result);
```
## Test fixtures
- For shared setup, write helper functions in a `tests/common/mod.rs` (not `tests/common.rs` — that runs as a test binary).
- Don't share mutable state across tests. They run in parallel by default.
## Async tests
- `#[tokio::test]` on `async fn` test functions. Needs the `tokio` test feature: `tokio = { ..., features = ["macros", "rt-multi-thread"] }`.
- For multi-thread runtime: `#[tokio::test(flavor = "multi_thread")]`.
- `#[tokio::test(start_paused = true)]` to control time deterministically.
## Property tests
- **proptest** or **quickcheck** for generated inputs:
```rust
proptest! {
#[test]
fn parse_roundtrip(s in "[a-z]+") {
assert_eq!(parse(&serialize(&s)).unwrap(), s);
}
}
```
- Property tests catch edge cases unit tests miss. Use them for parsers, serializers, math.
## Mocking
- **mockall** for mock implementations of traits. Generate from `#[automock]`.
- Don't mock concrete types — refactor to traits at the boundary, then mock those.
- For HTTP, use **wiremock** or **mockito** to stand up a fake server in-process.
## Database tests
- Use **testcontainers-rs** to spin up real Postgres/Redis/whatever. The boot cost is amortized over many tests.
- Each test creates its own schema or uses a transaction that rolls back at the end.
- Don't fake the database. Bugs hide in the impedance.
## Coverage
- `cargo llvm-cov` (or `cargo tarpaulin`) for coverage. Aim for high coverage on logic, not on every match arm.
- Coverage targets are signals, not goals. A 95% covered codebase with no integration tests has worse properties than 70% covered with good ones.
## CI
- `cargo test --all-features` and `cargo test --no-default-features` separately. Feature combinations are where bugs hide.
- `cargo clippy --all-targets --all-features -- -D warnings` is mandatory.
- Cache `target/` between runs. Rust builds are slow without it.
## Don't
- Don't `.unwrap()` everything in tests. `expect("explanation")` so failures point at the cause.
- Don't sleep in tests for timing. Use mocked time or signals.
- Don't depend on test execution order. They run in parallel; assume any order.
- Don't disable warnings to make a test compile. Fix the warning.
Other Rust templates
Modern Rust Rules
Idiomatic Rust: ownership, error handling with ?, traits, modules, cargo.
Rust + Axum Web Server
Axum routers, extractors, middleware, and structured error responses.
Rust + Tokio Async Patterns
Tokio fundamentals: tasks, select!, channels, cancellation, join!.
Rust CLI Tools (clap)
Build polished CLIs with clap derive, anyhow, and structured logging.