Rust Testing Code Review
by @anderskev
Reviews Rust test code for unit test patterns, integration test structure, async testing, mocking approaches, and property-based testing. Covers Rust 2024 ed...
clawhub install rust-testing-code-reviewπ About This Skill
name: rust-testing-code-review description: Reviews Rust test code for unit test patterns, integration test structure, async testing, mocking approaches, and property-based testing. Covers Rust 2024 edition changes including async fn in traits for mocks, #[expect] lint suppression, LazyLock test fixtures, and temporary scope changes affecting test assertions. Use when reviewing _test.rs files, #[cfg(test)] modules, or test infrastructure in Rust projects. Covers tokio::test, test fixtures, and assertion patterns.
Rust Testing Code Review
Review Workflow
1. Check Rust edition β Note edition in Cargo.toml (2021 vs 2024). Edition 2024 changes temporary scoping in if let and tail expressions, and makes #[expect] the preferred lint suppression
2. Check test organization β Unit tests in #[cfg(test)] modules, integration tests in tests/ directory
3. Check async test setup β #[tokio::test] for async tests, proper runtime configuration. Check for async-trait on mocks that could use native async fn in traits
4. Check assertions β Meaningful messages, correct assertion type. Review if let assertions for edition 2024 temporary scope changes
5. Check test isolation β No shared mutable state between tests, proper setup/teardown. Prefer LazyLock over lazy_static!/once_cell for shared fixtures
6. Check coverage patterns β Error paths tested, edge cases covered
Gates (hard)
Do not advance to Output Format until each pass condition is satisfied (yes/no with a concrete artifact).
1. Edition recorded β Open the target crateβs Cargo.toml (or workspace [workspace.package] / inherited edition) and note the edition value. Pass: you can quote edition = "β¦" (or document βinherited from workspaceβ) before citing Rust 2024βspecific behavior (if let / tail temporary drops, #[expect] vs #[allow] migration, native async fn in traits as default). If edition is not 2024, do not report those items as edition-2024 regressions; at most Informational if still useful.
2. dyn vs static async mocks β Before suggesting native async fn in traits instead of async-trait, check whether the mock is used as dyn Trait. Pass: if dyn is required, you either skip that suggestion or align with Valid Patterns (async-trait still needed).
3. Verification protocol β Pass: steps from beagle-rust:review-verification-protocol are done before any finding is listed (see Before Submitting Findings).
Output Format
Report findings as:
[FILE:LINE] ISSUE_TITLE
Severity: Critical | Major | Minor | Informational
Description of the issue and why it matters.
Quick Reference
| Issue Type | Reference |
|------------|-----------|
| Unit tests, assertions, naming, snapshots, rstest, doc tests, #[expect], LazyLock fixtures, tail expression scope | references/unit-tests.md |
| Integration tests, async testing, fixtures, test databases, native async fn mocks, if let temporary scope | references/integration-tests.md |
| Fuzzing, property-based testing, Miri, Loom, benchmarking, compile_fail, custom harness, mocking strategies | references/advanced-testing.md |
Review Checklist
Test Structure
#[cfg(test)] mod tests within source filestests/ directory (one file per module or feature)use super::* in test modules to access parent module itemstest___ Async Tests
#[tokio::test] used for async test functions#[tokio::test(flavor = "multi_thread")] when testing multi-threaded behaviorblock_on inside async tests (use .await directly)async fn instead of async-trait crate (stable since Rust 1.75)Assertions
assert_eq! / assert_ne! used for value comparisons (better error messages than assert!)matches! macro used for enum variant checkingmatches! or pattern matching, not string comparisonif let assertions reviewed for edition 2024 temporary scope β temporaries in conditions drop earlier, may invalidate borrowsMocking and Test Doubles
std::sync::LazyLock used for shared test fixtures instead of lazy_static! or once_cell (stable since Rust 1.80)Error Path Testing
Result::Err variants tested, not just happy paths#[should_panic] used sparingly β prefer Result-returning testsLint Suppression in Tests
#[expect(lint)] used instead of #[allow(lint)] for test-specific suppressions (stable since Rust 1.81)#[expect] or #[allow] in test code#[allow] attributes migrated to #[expect] for self-cleaning behaviorTest Naming
test_happy_path)mod blocks for organization_should__when_ Snapshot Testing
cargo insta used for complex structural output (JSON, YAML, HTML, CLI output)snapshots/ directoryassert_eq!, not snapshotsParametrized Testing
rstest used to avoid duplicated test functions for similar inputs#[rstest] with #[case::name] attributes for descriptive parametrized tests#[fixture] used for shared test setup when multiple tests need same construction#[case(1)])#[rstest] #[tokio::test] for async parametrized testsDoc Tests
/// # Examples with runnable code# to keep examples cleancargo test --doc passes (nextest doesn't run doc tests)Severity Calibration
Critical
Major
#[should_panic] without expected message (catches any panic, including wrong ones)unwrap() in test setup that hides the real failure locationif let with inline temporary in assertion that breaks under edition 2024 temporary scopingasync-trait on mock traits when native async fn in traits is available and project targets edition 2024Minor
assert!(x == y) instead of assert_eq!(x, y) (worse error messages)#[allow] used where #[expect] would provide self-cleaning suppressionlazy_static! or once_cell used for test fixtures when LazyLock is availableInformational
proptest or quickcheckValid Patterns (Do NOT Flag)
unwrap() / expect() in tests β Panicking on unexpected errors is the correct test behavioruse super::* in test modules β Standard pattern for accessing parent items#[allow(dead_code)] on test helpers β Helper functions may not be used in every testclone() in tests β Clarity over performanceassert! for boolean checks β Fine when the expression is clearly boolean (.is_some(), .is_empty())unwrap() on Result-returning test functions β Propagating with ? is also fine but not requiredasync-trait on mock traits requiring dyn dispatch β Native async fn in traits doesn't support dyn Trait; async-trait is still needed there#[expect] with justification on test helpers β Self-cleaning lint suppression is correct in test codeLazyLock for expensive shared test fixtures β Thread-safe lazy init is appropriate for test globalsBefore Submitting Findings
Load and follow beagle-rust:review-verification-protocol before reporting any issue.