🎁 Get the FREE AI Skills Starter Guide β€” Subscribe β†’
BytesAgainBytesAgain
πŸ¦€ ClawHub

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

Versionv1.0.5
Downloads723
Installs2
TERMINAL
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

  • [ ] Unit tests in #[cfg(test)] mod tests within source files
  • [ ] Integration tests in tests/ directory (one file per module or feature)
  • [ ] use super::* in test modules to access parent module items
  • [ ] Test function names describe the scenario: test___
  • [ ] Tests are independent β€” no reliance on execution order
  • Async Tests

  • [ ] #[tokio::test] used for async test functions
  • [ ] #[tokio::test(flavor = "multi_thread")] when testing multi-threaded behavior
  • [ ] No block_on inside async tests (use .await directly)
  • [ ] Test timeouts set for tests that could hang
  • [ ] Mock traits use native 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!)
  • [ ] Custom messages on assertions that aren't self-documenting
  • [ ] matches! macro used for enum variant checking
  • [ ] Error types checked with matches! or pattern matching, not string comparison
  • [ ] One assertion per test where practical (easier to diagnose failures)
  • [ ] if let assertions reviewed for edition 2024 temporary scope β€” temporaries in conditions drop earlier, may invalidate borrows
  • [ ] Tail expression returns reviewed for edition 2024 β€” temporaries in tail expressions drop before local variables
  • Mocking and Test Doubles

  • [ ] Traits used as seams for dependency injection (not concrete types)
  • [ ] Mock implementations kept minimal β€” only what the test needs
  • [ ] No mocking of types you don't own (wrap external dependencies behind your own trait)
  • [ ] Test fixtures as helper functions, not global state
  • [ ] 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
  • [ ] Specific error variants checked (not just "is error")
  • [ ] #[should_panic] used sparingly β€” prefer Result-returning tests
  • Lint Suppression in Tests

  • [ ] #[expect(lint)] used instead of #[allow(lint)] for test-specific suppressions (stable since Rust 1.81)
  • [ ] Justification comment on every #[expect] or #[allow] in test code
  • [ ] Stale #[allow] attributes migrated to #[expect] for self-cleaning behavior
  • Test Naming

  • [ ] Test names read like sentences describing behavior (not test_happy_path)
  • [ ] Related tests grouped in nested mod blocks for organization
  • [ ] Test names follow pattern: _should__when_
  • Snapshot Testing

  • [ ] cargo insta used for complex structural output (JSON, YAML, HTML, CLI output)
  • [ ] Snapshots are small and focused (not huge objects)
  • [ ] Redactions used for unstable fields (timestamps, UUIDs)
  • [ ] Snapshots committed to git in snapshots/ directory
  • [ ] Simple values use assert_eq!, not snapshots
  • Parametrized 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
  • [ ] Parametrized tests still have descriptive case names (not just #[case(1)])
  • [ ] Combined with async: #[rstest] #[tokio::test] for async parametrized tests
  • Doc Tests

  • [ ] Public API functions have /// # Examples with runnable code
  • [ ] Doc tests serve as both documentation and correctness checks
  • [ ] Hidden setup lines prefixed with # to keep examples clean
  • [ ] cargo test --doc passes (nextest doesn't run doc tests)
  • Severity Calibration

    Critical

  • Tests that pass but don't actually verify behavior (assertions on wrong values)
  • Shared mutable state between tests causing flaky results
  • Missing error path tests for security-critical code
  • Major

  • #[should_panic] without expected message (catches any panic, including wrong ones)
  • unwrap() in test setup that hides the real failure location
  • Tests that depend on execution order
  • if let with inline temporary in assertion that breaks under edition 2024 temporary scoping
  • async-trait on mock traits when native async fn in traits is available and project targets edition 2024
  • Minor

  • Missing assertion messages on complex comparisons
  • assert!(x == y) instead of assert_eq!(x, y) (worse error messages)
  • Test names that don't describe the scenario
  • Redundant setup code that could be extracted to a helper
  • #[allow] used where #[expect] would provide self-cleaning suppression
  • lazy_static! or once_cell used for test fixtures when LazyLock is available
  • Informational

  • Suggestions to add property-based tests via proptest or quickcheck
  • Suggestions to add snapshot testing for complex output
  • Coverage improvement opportunities
  • Valid Patterns (Do NOT Flag)

  • unwrap() / expect() in tests β€” Panicking on unexpected errors is the correct test behavior
  • use super::* in test modules β€” Standard pattern for accessing parent items
  • #[allow(dead_code)] on test helpers β€” Helper functions may not be used in every test
  • clone() in tests β€” Clarity over performance
  • Large test functions β€” Integration tests can be long; extracting helpers isn't always clearer
  • assert! for boolean checks β€” Fine when the expression is clearly boolean (.is_some(), .is_empty())
  • Multiple assertions testing one logical behavior β€” Sometimes one behavior needs multiple checks
  • unwrap() on Result-returning test functions β€” Propagating with ? is also fine but not required
  • async-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 code
  • LazyLock for expensive shared test fixtures β€” Thread-safe lazy init is appropriate for test globals
  • Before Submitting Findings

    Load and follow beagle-rust:review-verification-protocol before reporting any issue.