rust-testing
Rust testing patterns — unit tests with mockall, integration tests with sqlx transactions, HTTP handler testing (axum), benchmarks (criterion), property tests (proptest), fuzzing, and CI with cargo-nextest.
Best use case
rust-testing is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Rust testing patterns — unit tests with mockall, integration tests with sqlx transactions, HTTP handler testing (axum), benchmarks (criterion), property tests (proptest), fuzzing, and CI with cargo-nextest.
Teams using rust-testing should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/rust-testing/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How rust-testing Compares
| Feature / Agent | rust-testing | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Rust testing patterns — unit tests with mockall, integration tests with sqlx transactions, HTTP handler testing (axum), benchmarks (criterion), property tests (proptest), fuzzing, and CI with cargo-nextest.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Rust Testing Patterns
Comprehensive testing strategies for Rust applications following TDD methodology.
## When to Activate
- Writing unit tests with mock dependencies
- Testing database-dependent code with sqlx
- Testing axum HTTP handlers
- Setting up benchmarks with criterion
- Configuring cargo-nextest in CI/CD
- Using `mockall::automock` to generate mock implementations from trait definitions
- Writing property-based tests with `proptest` to verify invariants across randomly generated inputs
- Isolating integration tests using `#[sqlx::test]` for automatic per-test transaction rollback
## TDD in Rust
```
RED → Write a failing #[test]
GREEN → Write minimal implementation
REFACTOR → Improve while keeping tests green
```
## Unit Tests (Co-located)
The idiomatic Rust approach: unit tests live in the same file, in a `#[cfg(test)]` module.
```rust
// src/domain/discount.rs
pub fn apply_discount(price: f64, tier: CustomerTier) -> f64 {
match tier {
CustomerTier::Standard => price,
CustomerTier::Silver => price * 0.95,
CustomerTier::Gold => price * 0.90,
CustomerTier::Platinum => price * 0.80,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn standard_tier_no_discount() {
assert_eq!(apply_discount(100.0, CustomerTier::Standard), 100.0);
}
#[test]
fn platinum_tier_20_percent_off() {
assert_eq!(apply_discount(100.0, CustomerTier::Platinum), 80.0);
}
#[test]
fn discount_rounds_correctly() {
let result = apply_discount(33.33, CustomerTier::Gold);
assert!((result - 30.0).abs() < 0.01);
}
}
```
## Mocking with mockall
```toml
# Cargo.toml
[dev-dependencies]
mockall = "0.13"
```
```rust
// Define trait with #[automock] in production code
use mockall::automock;
#[automock] // Generates MockUserRepository
#[async_trait::async_trait]
pub trait UserRepository: Send + Sync {
async fn find_by_id(&self, id: i64) -> Result<Option<User>, DbError>;
async fn save(&self, user: &NewUser) -> Result<User, DbError>;
async fn delete(&self, id: i64) -> Result<(), DbError>;
}
// Test using the generated mock
#[cfg(test)]
mod tests {
use super::*;
use mockall::predicate::*;
#[tokio::test]
async fn register_user_saves_and_returns_user() {
let mut mock = MockUserRepository::new();
mock.expect_save()
.with(predicate::function(|u: &NewUser| u.email == "alice@test.com"))
.times(1)
.returning(|u| Ok(User { id: 1, email: u.email.clone(), name: u.name.clone() }));
let service = UserService::new(Arc::new(mock));
let user = service.register("Alice", "alice@test.com").await.unwrap();
assert_eq!(user.id, 1);
assert_eq!(user.email, "alice@test.com");
}
#[tokio::test]
async fn register_returns_error_on_db_failure() {
let mut mock = MockUserRepository::new();
mock.expect_save()
.times(1)
.returning(|_| Err(DbError::ConnectionFailed));
let service = UserService::new(Arc::new(mock));
let result = service.register("Alice", "alice@test.com").await;
assert!(result.is_err());
}
}
```
### mockall Predicates
```rust
use mockall::predicate::*;
// Exact value
.with(eq(42))
.with(eq("hello"))
// Custom predicate
.with(function(|x: &i32| *x > 0))
// String contains
.with(str::contains("@"))
// Multiple arguments
.with(eq(1), eq("name"))
// Any value (don't care)
.with(always())
// Call count
.times(1) // exactly once
.times(2..=5) // 2 to 5 times
.once() // sugar for .times(1)
.never() // must not be called
```
## Integration Tests with sqlx
```toml
# Cargo.toml
[dev-dependencies]
sqlx = { version = "0.8", features = ["postgres", "runtime-tokio", "macros", "migrate"] }
tokio = { version = "1", features = ["full"] }
```
```rust
// tests/user_repository_test.rs
use sqlx::PgPool;
// Helper: create an isolated test transaction
async fn setup_db() -> PgPool {
let url = std::env::var("TEST_DATABASE_URL")
.expect("TEST_DATABASE_URL must be set for integration tests");
let pool = PgPool::connect(&url).await.unwrap();
sqlx::migrate!("./migrations").run(&pool).await.unwrap();
pool
}
#[sqlx::test] // sqlx::test handles setup/teardown with isolated transactions
async fn find_user_by_id_returns_none_when_not_found(pool: PgPool) {
let repo = PostgresUserRepo::new(pool);
let result = repo.find_by_id(9999).await.unwrap();
assert!(result.is_none());
}
#[sqlx::test]
async fn save_and_find_by_id(pool: PgPool) {
let repo = PostgresUserRepo::new(pool);
let saved = repo.save(&NewUser {
name: "Alice".to_string(),
email: "alice@test.com".to_string(),
}).await.unwrap();
assert!(saved.id > 0);
let found = repo.find_by_id(saved.id).await.unwrap();
assert_eq!(found.unwrap().email, "alice@test.com");
}
#[sqlx::test]
async fn delete_removes_user(pool: PgPool) {
let repo = PostgresUserRepo::new(pool);
let user = repo.save(&NewUser { name: "Bob".to_string(), email: "b@test.com".to_string() })
.await.unwrap();
repo.delete(user.id).await.unwrap();
let result = repo.find_by_id(user.id).await.unwrap();
assert!(result.is_none());
}
```
## HTTP Handler Testing (axum)
```rust
// tests/user_api_test.rs
use axum::{
body::Body,
http::{Request, StatusCode},
};
use tower::ServiceExt; // oneshot()
use serde_json::{json, Value};
fn test_app() -> axum::Router {
let state = AppState {
repo: Arc::new(InMemoryUserRepo::new()),
config: Arc::new(Config::test()),
};
router(state)
}
#[tokio::test]
async fn get_user_returns_200() {
let app = test_app();
// Pre-seed data
let create_resp = app.clone()
.oneshot(
Request::builder()
.method("POST")
.uri("/users")
.header("content-type", "application/json")
.body(Body::from(json!({"name": "Alice", "email": "a@test.com"}).to_string()))
.unwrap()
)
.await
.unwrap();
assert_eq!(create_resp.status(), StatusCode::CREATED);
let body: Value = serde_json::from_slice(
&axum::body::to_bytes(create_resp.into_body(), usize::MAX).await.unwrap()
).unwrap();
let user_id = body["id"].as_i64().unwrap();
// Fetch
let response = app
.oneshot(
Request::builder()
.uri(format!("/users/{user_id}"))
.body(Body::empty())
.unwrap()
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let body: Value = serde_json::from_slice(
&axum::body::to_bytes(response.into_body(), usize::MAX).await.unwrap()
).unwrap();
assert_eq!(body["name"], "Alice");
}
#[tokio::test]
async fn get_user_returns_404_when_not_found() {
let app = test_app();
let response = app
.oneshot(Request::builder().uri("/users/9999").body(Body::empty()).unwrap())
.await
.unwrap();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
}
```
## Property-Based Tests (proptest)
```toml
[dev-dependencies]
proptest = "1"
```
```rust
use proptest::prelude::*;
proptest! {
// Property: parse → serialize → parse is idempotent
#[test]
fn email_roundtrip(
local in "[a-z]{1,20}",
domain in "[a-z]{2,10}"
) {
let raw = format!("{local}@{domain}.com");
let email = Email::parse(&raw).unwrap();
assert_eq!(email.as_str(), raw);
}
// Property: sorted is always ordered
#[test]
fn sort_is_ordered(mut values: Vec<i32>) {
values.sort();
for i in 1..values.len() {
assert!(values[i-1] <= values[i]);
}
}
// Property: discount never exceeds original price
#[test]
fn discount_never_exceeds_price(price in 0.01f64..1_000_000.0) {
let discounted = apply_discount(price, CustomerTier::Platinum);
assert!(discounted <= price);
assert!(discounted >= 0.0);
}
}
```
## Benchmarks (criterion)
```toml
[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }
[[bench]]
name = "my_bench"
harness = false
```
```rust
// benches/my_bench.rs
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
fn bench_sort(c: &mut Criterion) {
let mut group = c.benchmark_group("sort");
for size in [10, 100, 1000, 10_000].iter() {
group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, &size| {
let data: Vec<i32> = (0..size).rev().collect();
b.iter(|| {
let mut v = data.clone();
v.sort();
black_box(v)
});
});
}
group.finish();
}
fn bench_string_format(c: &mut Criterion) {
c.bench_function("format_email", |b| {
b.iter(|| format!("{}@{}.com", black_box("alice"), black_box("example")))
});
}
criterion_group!(benches, bench_sort, bench_string_format);
criterion_main!(benches);
```
```bash
# Run benchmarks
cargo bench
# Run specific benchmark
cargo bench -- bench_sort
# Save baseline
cargo bench -- --save-baseline before
# Make changes, then compare
cargo bench -- --baseline before
```
## Test Organization
```
src/
lib.rs # #[cfg(test)] mod tests { } — unit tests co-located
domain/
user.rs # unit tests inside
order.rs
tests/ # Integration tests — only use public API
common/
mod.rs # Shared helpers: setup_db(), build_app()
user_api.rs # Full HTTP roundtrip tests
user_repo.rs # Repository integration tests
benches/ # criterion benchmarks
throughput.rs
```
### Shared Test Helpers
```rust
// tests/common/mod.rs
use sqlx::PgPool;
pub async fn test_pool() -> PgPool {
let url = std::env::var("TEST_DATABASE_URL").unwrap();
let pool = PgPool::connect(&url).await.unwrap();
sqlx::migrate!("./migrations").run(&pool).await.unwrap();
pool
}
pub fn test_user() -> NewUser {
NewUser {
name: "Test User".to_string(),
email: format!("test-{}@example.com", uuid::Uuid::new_v4()),
}
}
```
## CLI Quick Reference
```bash
# Run all tests
cargo test
# Run specific test
cargo test test_name
# Run tests in a module
cargo test domain::
# Show println! output
cargo test -- --nocapture
# Run only ignored tests
cargo test -- --ignored
# Parallel test count
cargo test -- --test-threads=4
# Using cargo-nextest (much faster in CI)
cargo nextest run
cargo nextest run --test-threads=8
# With test coverage (llvm-cov)
cargo llvm-cov
cargo llvm-cov --html
# Fuzzing
cargo fuzz add fuzz_target_1
cargo fuzz run fuzz_target_1
# Benchmark
cargo bench
```
## CI/CD with cargo-nextest
```yaml
# .github/workflows/test.yml
- name: Install nextest
uses: taiki-e/install-action@nextest
- name: Run tests
run: cargo nextest run --profile ci
- name: Run benchmarks (verify compile)
run: cargo bench --no-run
```
```toml
# .config/nextest.toml
[profile.ci]
fail-fast = false
test-threads = "num-cpus"
status-level = "fail"
```
## Quick Reference
| Scenario | Tool/Pattern |
|----------|-------------|
| Unit test | `#[test]` in `#[cfg(test)]` module |
| Async test | `#[tokio::test]` |
| Mock trait | `mockall::automock` |
| DB integration | `#[sqlx::test]` (isolated transaction) |
| HTTP handler | `axum` + `tower::ServiceExt::oneshot` |
| Property test | `proptest!` macro |
| Coverage | `cargo llvm-cov` |
For anti-patterns and common mistakes, see skill `rust-testing-advanced`.Related Skills
zero-trust-patterns
Zero-Trust security patterns — mTLS between microservices (Istio/SPIFFE), SPIRE workload identity, OPA/Envoy authorization, NetworkPolicy default-deny-all, short-lived credentials, service mesh security, and Kubernetes RBAC hardening.
visual-testing
Visual Regression Testing: tool comparison (Chromatic/Percy/Playwright screenshots/BackstopJS), pixel-diff vs AI-based comparison, baseline management, flakiness strategies (masks, tolerances, waitForLoadState), CI integration with GitHub Actions, and Storybook integration.
typescript-testing
TypeScript testing patterns: Vitest for unit/integration, Playwright for E2E, MSW for API mocking, Testing Library for React components. Core TDD methodology for TypeScript/JavaScript projects.
swift-testing
Swift testing patterns: Swift Testing framework (Swift 6+), XCTest for UI tests, async/await test cases, actor testing, Combine testing, and XCUITest for UI automation. TDD for Swift/SwiftUI.
swift-protocol-di-testing
Protocol-based dependency injection for testable Swift code — mock file system, network, and external APIs using focused protocols and Swift Testing.
scala-testing
Scala testing with ScalaTest, MUnit, and ScalaCheck: FunSpec/FlatSpec test structure, property-based testing with forAll, mocking with MockitoSugar, Cats Effect testing with munit-cats-effect (runTest/IOSuite), ZIO Test, Testcontainers-Scala for database integration tests, and CI integration with sbt. Use when writing or reviewing Scala tests.
rust-web-patterns
Axum HTTP handlers, Serde serialization, async channels, iterator patterns, trait objects, configuration, and WebAssembly target for Rust web services.
rust-testing-advanced
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
rust-patterns
Idiomatic Rust patterns, ownership idioms, async with Tokio, error handling with thiserror/anyhow, testing strategies, and hexagonal architecture in Rust.
rust-patterns-advanced
Advanced Rust patterns — zero-cost abstractions, proc macros, unsafe FFI, WASM, Axum web architecture, trait objects vs generics, and performance profiling.
ruby-testing
RSpec testing patterns for Ruby and Rails — factories, mocks, request specs, feature specs, VCR, and SimpleCov coverage.
r-testing
R testing patterns: testthat 3e with expect_* assertions, snapshot testing, mocking with mockery and httptest2, covr code coverage, lintr static analysis, property-based testing with hedgehog, testing Shiny apps with shinytest2. Use when writing or reviewing R tests.