rust-testing

star 2

Use when writing unit/integration tests, setting up test infrastructure, mocking with wiremock/mockall, or designing test structure in Rust.

longzhi By longzhi schedule Updated 3/7/2026

name: rust-testing description: > Use when writing unit/integration tests, setting up test infrastructure, mocking with wiremock/mockall, or designing test structure in Rust.

Rust Testing Standards

Test Categories

Type Location Dependencies
Unit tests #[cfg(test)] module in source file None (no external deps)
Integration tests tests/ directory May need database/services

Commands

cargo test              # all tests
cargo test --lib        # unit tests only
cargo test --test '*'   # integration tests only
cargo test -- --nocapture  # show stdout

Unit Tests

Place in a #[cfg(test)] module at the bottom of the source file:

impl Order {
    pub fn can_cancel(&self) -> bool {
        matches!(self.status, OrderStatus::Pending | OrderStatus::Processing)
    }
}

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

    #[test]
    fn test_can_cancel_pending_order() {
        let order = Order { status: OrderStatus::Pending, ..Default::default() };
        assert!(order.can_cancel());
    }
}

Naming Convention

// Format: test_{feature}_{scenario}_{expected}
fn test_place_order_with_insufficient_balance_returns_error() { }

// Or "should" style
fn should_return_error_when_balance_insufficient() { }

Integration Tests

tests/
├── common/
│   └── mod.rs              # shared helpers (setup_test_db, etc.)
├── api/
│   └── orders_test.rs
└── integration/
    └── order_flow_test.rs

HTTP Test Example

#[tokio::test]
async fn test_create_order_success() {
    let pool = setup_test_db().await;
    let server = TestServer::new(create_test_app(pool.clone()).await).unwrap();

    let response = server
        .post("/orders")
        .add_header("Authorization", format!("Bearer {}", api_key))
        .json(&json!({ "product_id": "prod-001", "quantity": 2 }))
        .await;

    response.assert_status(StatusCode::CREATED);
}

Mocking

Trait Abstraction (hand-written mock)

#[async_trait]
pub trait ExternalApiClient: Send + Sync {
    async fn submit(&self, req: &SubmitRequest) -> Result<SubmitResponse, Error>;
}

// Test implementation
pub struct MockExternalApiClient {
    pub responses: Arc<Mutex<Vec<Result<SubmitResponse, Error>>>>,
}

mockall Crate (recommended alternative)

For most cases, prefer mockall over hand-written mocks — it auto-generates mock structs from traits:

use mockall::automock;

#[automock]
#[async_trait]
pub trait ExternalApiClient: Send + Sync {
    async fn submit(&self, req: &SubmitRequest) -> Result<SubmitResponse, Error>;
}

#[tokio::test]
async fn test_submit_delegates_to_client() {
    let mut mock = MockExternalApiClient::new();
    mock.expect_submit()
        .returning(|_| Ok(SubmitResponse { id: "abc-123".into() }));

    let service = OrderService::new(Arc::new(mock));
    let result = service.process(&request).await.unwrap();
    assert_eq!(result.id, "abc-123");
}

wiremock (HTTP-level mocking)

#[tokio::test]
async fn test_external_api_client() {
    let mock_server = MockServer::start().await;

    Mock::given(method("POST"))
        .and(path("/api/process"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "id": "abc-123"
        })))
        .mount(&mock_server)
        .await;

    let client = ExternalApiClient::new(&mock_server.uri());
    let result = client.submit(&request).await.unwrap();
    assert_eq!(result.id, "abc-123");
}

Key Coverage Targets

  1. State machine transitions: e.g., Order status flow (Pending -> Confirmed -> Shipped -> Delivered)
  2. Billing logic: balance deduction, refunds, proration
  3. Authorization checks: API key validation, role-based access
  4. Boundary conditions: null/empty values, limits exceeded, concurrency

Property-Based Testing (advanced)

For logic with wide input ranges, consider proptest to auto-generate test cases:

use proptest::prelude::*;

proptest! {
    #[test]
    fn test_discount_never_exceeds_total(
        price in 1u64..10_000,
        discount_pct in 0u8..=100,
    ) {
        let discounted = apply_discount(price, discount_pct);
        prop_assert!(discounted <= price);
    }
}

proptest is especially useful for parsers, serialization round-trips, and numeric invariants.

Anti-Patterns

// WRONG: unwrap without context
let result = operation().unwrap();
// RIGHT: use expect
let result = operation().expect("operation should succeed with valid input");

// WRONG: hard-coded sleep
std::thread::sleep(Duration::from_secs(1));
// RIGHT: conditional wait with timeout
tokio::time::timeout(Duration::from_secs(5), wait_for_condition()).await;

// WRONG: tests depend on execution order
// RIGHT: each test is fully independent

Checklist

  • Unit tests live in #[cfg(test)] modules
  • Integration tests live in tests/ directory
  • External dependencies are mocked (mockall or hand-written traits)
  • Test database is cleaned between runs
  • Async tests use #[tokio::test]
  • Tests are independent of each other
Install via CLI
npx skills add https://github.com/longzhi/rust-coding-standards --skill rust-testing
Repository Details
star Stars 2
call_split Forks 1
navigation Branch main
article Path SKILL.md
More from Creator