Mocking & Test Doubles
Rust favorece "seams" baseados em traits e fakes manuais em vez de frameworks de mocking pesados. mockall gera implementações mock para traits quando você precisa de expectativas de chamada e verificação.
Receita
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()));Quando usar isso: Limites de I/O externos (HTTP, pagamentos, relógio) que precisam ser trocados em testes.
Exemplo Funcional
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();
}O que isso demonstra:
- Serviço genérico aceita implementações de trait
expect_*define contagem de chamadas e argumentos- Produção usa tipos reais; testes injetam mocks
- Traits assíncronos precisam de
#[async_trait]ou traits assíncronos nativos do Rust 1.75+
Mergulho Profundo
Fake vs Mock
| Double | Quando |
|---|---|
| Fake (mapa em memória) | Comportamento simples com estado |
| Stub (respostas fixas) | Poucos padrões de chamada |
Mock (mockall) | Ordem e contagens de chamada estritas |
| Spy (grava chamadas) | Verifica efeitos colaterais de forma flexível |
Exemplo de Fake Manual
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(())
}
}- Frequentemente mais claro que mocks para lógica de domínio
- Sem geração de código; controle total sobre o comportamento
Armadilhas
- Mocking de structs concretas - não é possível sem traits. Correção: extraia o limite de
traitem tempo de compilação. - Especificar demais a ordem das chamadas - testes frágeis. Correção: afirme os resultados, não cada chamada interna.
- Mock de trait assíncrono sem
async_trait- erros confusos. Correção: habilite o suporte assíncrono domockallou use trait síncrono +block_onapenas em testes. - Mocks
Arc<dyn Trait>compartilhados - corridas de expectativa em testes paralelos. Correção: instância de mock por teste. - Mocking de tipos
std- não é possível. Correção: envolvaInstant,Uuidou HTTP em seu próprio trait.
Alternativas
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
| Fake em memória | Repositórios CRUD | Verificação de contagens exatas de chamadas |
wiremock | Testes de cliente HTTP | Lógica de domínio pura |
testcontainers | Comportamento real do Postgres | Feedback rápido de unidade |
FAQs
mockall é obrigatório?
Não. Fakes e stubs manuais são idiomáticos; mockall ajuda quando as expectativas importam.
Como faço mock de traits genéricos?
mockall suporta genéricos com #[automock] no trait; verifique a documentação para limites de tipos associados.
Posso esperar chamadas assíncronas?
Sim, com suporte a trait assíncrono; garanta o runtime em #[tokio::test].
E métodos estáticos?
Prefira traits de instância; para APIs estritamente estáticas, injete um tipo token de tamanho zero que implemente um trait.
Como testar erros?
returning(|_| Err(Error::Timeout)) na expectativa.
Mockall com dyn Trait?
MockFoo::new() implementa Foo; passe como mock concreto ou Arc<dyn Foo> com cuidado.
Como evitar que mocks vazem para produção?
Mantenha os traits em src; mocks gerados no mesmo módulo sob #[cfg(test)] ou módulo apenas de teste.
Estratégia de mocking HTTP?
Trait HttpClient para testes de unidade; wiremock para HTTP em nível de integração.
Mocking de tempo?
Trait Clock com now() -> Instant; fake fixo em testes.
Quando não fazer mock?
Funções puras, serializadores e algoritmos - use asserções diretas em vez disso.
Relacionado
- Organização de Testes - fakes compartilhados
- Abstração Baseada em Trait - "seams" de design
- Testando Código Assíncrono - testes assíncronos
- Testes de Unidade vs. Integração - onde os doubles vivem
Versões da Stack: Esta página foi escrita para Rust 1.97.0 (edição 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4 e Polars 0.46+.