Mocking & Test Doubles
Rust favors trait-based seams and manual fakes over heavy mocking frameworks. mockall generates mock implementations for traits when you need call expectations and verification.
Busca en todas las páginas de la documentación
Rust favors trait-based seams and manual fakes over heavy mocking frameworks. mockall generates mock implementations for traits when you need call expectations and verification.
use mockall::automock;
#[automock]
pub trait PaymentGateway {
async fn charge(&self, cents: u64) -> Result<String, Error>;
}let mut mock = MockPaymentGateway::new();
mock.expect_charge().returning(|_| Ok("ch_123".into()));When to reach for this: External I/O boundaries (HTTP, payments, clock) that must be swapped in tests.
use mockall::automock;
#[automock]
pub trait OrderRepo {
async fn save(&self, order: &Order) -> Result<(), Error>;
}
pub struct Service<G: PaymentGateway, R: OrderRepo> {
pay: G,
repo: R,
}
impl<G: PaymentGateway, R: OrderRepo> Service<G, R> {
pub async fn checkout(&self, order: &Order) -> Result<(), Error> {
self.pay.charge(order.total_cents).await?;
self.repo.save(order).await
}
}
#[tokio::test]
async fn checkout_charges_then_saves() {
let mut pay = MockPaymentGateway::new();
pay.expect_charge().with(eq(500)).returning(|_| Ok("ok".into()));
let mut repo = MockOrderRepo::new();
repo.expect_save().times(1).returning(|_| Ok(()));
let svc = Service { pay, repo };
svc.checkout(&Order { total_cents: 500 }).await.unwrap();
}What this demonstrates:
expect_* sets call count and arguments#[async_trait] or Rust 1.75+ native async traits| Double | When |
|---|---|
| Fake (in-memory map) | Simple stateful behavior |
| Stub (fixed responses) | Few call patterns |
Mock (mockall) | Strict call order and counts |
| Spy (record calls) | Verify side effects loosely |
struct InMemoryRepo {
orders: Mutex<Vec<Order>>,
}
impl OrderRepo for InMemoryRepo {
async fn save(&self, order: &Order) -> Result<(), Error> {
self.orders.lock().unwrap().push(order.clone());
Ok(())
}
}trait boundary at compile time.async_trait - confusing errors. Fix: enable mockall async support or use sync trait + block_on in tests only.Arc<dyn Trait> mocks - expectation races in parallel tests. Fix: per-test mock instance.Instant, Uuid, or HTTP in your own trait.| Alternative | Use When | Don't Use When |
|---|---|---|
| In-memory fake | CRUD repos | Verifying exact call counts |
wiremock | HTTP client tests | Pure domain logic |
testcontainers | Real Postgres behavior | Fast unit feedback |
No. Manual fakes and stubs are idiomatic; mockall helps when expectations matter.
mockall supports generics with #[automock] on trait; check docs for associated type limits.
Yes with async trait support; ensure runtime in #[tokio::test].
Prefer instance traits; for static-only APIs, inject a zero-sized token type implementing a trait.
returning(|_| Err(Error::Timeout)) on the expectation.
MockFoo::new() implements Foo; pass as concrete mock or Arc<dyn Foo> with care.
Keep traits in src; mocks generated in same module under #[cfg(test)] or test-only module.
Trait HttpClient for unit tests; wiremock for integration-level HTTP.
Trait Clock with now() -> Instant; fixed fake in tests.
Pure functions, serializers, and algorithms - use direct assertions instead.
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.
Revisado por Chris St. John·Última actualización: 16 jul 2026