Advanced Rust testing anti-patterns and corrections — cfg(test) placement, expect() over unwrap(), mockall expectation ordering, executor mixing (#[tokio::test] vs block_on), PgPool isolation with
From clarcnpx claudepluginhub marvinrichter/clarc --plugin clarcThis skill uses the workspace's default tool permissions.
Designs and optimizes AI agent action spaces, tool definitions, observation formats, error recovery, and context for higher task completion rates.
Enables AI agents to execute x402 payments with per-task budgets, spending controls, and non-custodial wallets via MCP tools. Use when agents pay for APIs, services, or other agents.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
This skill extends rust-testing with anti-patterns and corrections. Load rust-testing first.
Wrong:
// src/domain/discount.rs
pub fn apply_discount(price: f64, tier: CustomerTier) -> f64 { /* ... */ }
#[test] // Compiles into release builds — wastes binary size
fn standard_no_discount() {
assert_eq!(apply_discount(100.0, CustomerTier::Standard), 100.0);
}
Correct:
pub fn apply_discount(price: f64, tier: CustomerTier) -> f64 { /* ... */ }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn standard_no_discount() {
assert_eq!(apply_discount(100.0, CustomerTier::Standard), 100.0);
}
}
Why: Without #[cfg(test)], test code and its dependencies are compiled into release binaries, increasing binary size and compile time.
Wrong:
#[test]
fn save_user() {
let user = repo.save(&new_user()).await.unwrap(); // Panic message is useless
assert_eq!(user.email, "alice@test.com");
}
Correct:
#[test]
async fn save_user() {
let user = repo.save(&new_user()).await
.expect("saving a valid user should not fail");
assert_eq!(user.email, "alice@test.com");
}
Why: expect("context") produces a meaningful panic message that explains what went wrong at the test boundary, making failures far easier to diagnose.
Wrong:
let service = UserService::new(Arc::new(mock)); // mock moved in
mock.expect_save().returning(|_| Ok(saved_user())); // compile error: already moved
Correct:
let mut mock = MockUserRepository::new();
mock.expect_save()
.times(1)
.returning(|_| Ok(saved_user()));
let service = UserService::new(Arc::new(mock)); // move after setup
Why: Expectations must be configured before the mock is moved into the service under test; setting them up first also makes the test intent readable at a glance.
Wrong:
#[test]
fn fetch_returns_user() {
let result = futures::executor::block_on(service.fetch(1)); // Wrong executor
assert!(result.is_ok());
}
Correct:
#[tokio::test]
async fn fetch_returns_user() {
let result = service.fetch(1).await;
assert!(result.is_ok());
}
Why: Mixing executors causes panics or silent hangs; #[tokio::test] provides a proper single-threaded Tokio runtime that matches the runtime used in production.
Wrong:
// tests/common/mod.rs
static POOL: Lazy<PgPool> = Lazy::new(|| { /* ... */ });
// Tests share state — inserts from one test pollute another
Correct:
#[sqlx::test] // sqlx::test injects a fresh, isolated pool per test
async fn find_by_id_returns_none(pool: PgPool) {
let repo = PostgresUserRepo::new(pool);
assert!(repo.find_by_id(9999).await.unwrap().is_none());
}
Why: #[sqlx::test] wraps each test in its own transaction that is rolled back after the test, guaranteeing isolation without manual cleanup.
rust-testing — core testing patterns (unit, mocks, integration, benchmarks, CI)