# Rust Testing

> When to activate: Rust tests, unit tests, integration tests, doc tests, benchmarks, proptest, test helpers, test organization

- Skill: `mattakushi432/rust-testing` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mattakushi432/rust-testing`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mattakushi432/rust-testing/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: Mattakushi432 (https://skillmd.com/u/mattakushi432)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mattakushi432/rust-testing

---


# Rust Testing Patterns

## Unit Tests

Place unit tests in the same file as the code under test inside a `#[cfg(test)]` module.

```rust
pub fn add(a: i32, b: i32) -> i32 { a + b }

pub fn divide(a: f64, b: f64) -> Option<f64> {
    if b == 0.0 { None } else { Some(a / b) }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn add_positive_numbers() {
        assert_eq!(add(2, 3), 5);
    }

    #[test]
    fn divide_by_zero_returns_none() {
        assert_eq!(divide(10.0, 0.0), None);
    }

    #[test]
    #[should_panic(expected = "index out of bounds")]
    fn panics_on_bad_index() {
        let v: Vec<i32> = vec![];
        let _ = v[0];
    }
}
```

## Integration Tests

Live in the `tests/` directory at the crate root; each file is a separate crate.

```rust
// tests/api_tests.rs
use my_crate::{App, Config};

fn setup_app() -> App {
    App::new(Config { db_url: ":memory:".into(), port: 0 })
}

#[test]
fn creates_user_and_retrieves_it() {
    let app = setup_app();
    let id = app.create_user("alice@example.com").unwrap();
    let user = app.get_user(id).unwrap();
    assert_eq!(user.email, "alice@example.com");
}

// Shared test helpers go in tests/common/mod.rs
```

## Doc Tests

Code examples in doc comments are compiled and run as tests.

```rust
/// Adds two numbers together.
///
/// # Examples
///
/// ```
/// use my_crate::add;
/// assert_eq!(add(2, 3), 5);
/// assert_eq!(add(-1, 1), 0);
/// ```
pub fn add(a: i32, b: i32) -> i32 { a + b }
```

## Property-Based Testing with proptest

```toml
# Cargo.toml
[dev-dependencies]
proptest = "1"
```

```rust
use proptest::prelude::*;

fn sort_and_dedup(mut v: Vec<i32>) -> Vec<i32> {
    v.sort();
    v.dedup();
    v
}

proptest! {
    #[test]
    fn sorted_output_is_actually_sorted(v in prop::collection::vec(any::<i32>(), 0..100)) {
        let result = sort_and_dedup(v);
        for window in result.windows(2) {
            prop_assert!(window[0] <= window[1]);
        }
    }

    #[test]
    fn dedup_removes_consecutive_duplicates(v in prop::collection::vec(any::<i32>(), 0..100)) {
        let result = sort_and_dedup(v);
        for window in result.windows(2) {
            prop_assert_ne!(window[0], window[1]);
        }
    }
}
```

## Async Tests with tokio

```toml
[dev-dependencies]
tokio = { version = "1", features = ["full", "test-util"] }
```

```rust
#[tokio::test]
async fn fetches_data_successfully() {
    let client = HttpClient::new();
    let response = client.get("https://httpbin.org/json").await.unwrap();
    assert_eq!(response.status(), 200);
}

// Control time in tests
#[tokio::test]
async fn timeout_fires_after_delay() {
    tokio::time::pause();
    let result = tokio::time::timeout(
        std::time::Duration::from_secs(1),
        async {
            tokio::time::sleep(std::time::Duration::from_secs(10)).await;
        },
    ).await;
    tokio::time::advance(std::time::Duration::from_secs(2)).await;
    assert!(result.is_err());
}
```

## Test Fixtures and Cleanup Guards

```rust
struct TestDb { path: std::path::PathBuf }

impl TestDb {
    fn new() -> Self {
        let path = std::env::temp_dir().join(format!("test_{}.db", rand_suffix()));
        Self { path }
    }
}

impl Drop for TestDb {
    fn drop(&mut self) {
        let _ = std::fs::remove_file(&self.path);
    }
}

#[test]
fn database_stores_users() {
    let db = TestDb::new();
    // cleanup automatic on drop
}
```

## Benchmarks with Criterion

```toml
[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }

[[bench]]
name = "my_benchmark"
harness = false
```

```rust
// benches/my_benchmark.rs
use criterion::{black_box, criterion_group, criterion_main, Criterion, BenchmarkId};

fn bench_fibonacci(c: &mut Criterion) {
    let mut group = c.benchmark_group("fibonacci");
    for i in [10u64, 20, 30].iter() {
        group.bench_with_input(BenchmarkId::from_parameter(i), i, |b, &i| {
            b.iter(|| fibonacci(black_box(i)));
        });
    }
    group.finish();
}

criterion_group!(benches, bench_fibonacci);
criterion_main!(benches);
```

## Common Anti-Patterns

- **Testing private implementation details** — test through the public API
- **Sharing mutable state across parallel tests** — use `#[serial_test]` crate or redesign
- **Using real network/filesystem without isolation** — use `tempfile` crate or mock traits
- **`unwrap()` in tests without context** — prefer `expect("why this should succeed")`
- **Non-deterministic test order dependence** — each test must be fully independent

