Mocking em Java com Mockito
Simule dependências em testes Java com Mockito — mock, when/thenReturn, verify e ArgumentCaptor com exemplos práticos.
Um teste unitário deve exercitar uma classe de forma isolada. Mas classes reais dependem de colaboradores — um banco de dados, um gateway de pagamento, um serviço de e-mail — que são lentos, não confiáveis ou têm efeitos colaterais indesejados em testes. O Mockito é a biblioteca Java mais utilizada para substituir esses colaboradores por mocks: objetos substitutos que você programa para retornar respostas predefinidas e depois interroga sobre como foram chamados. Este capítulo apresenta a API do Mockito que você usará no dia a dia e comprova o conceito subjacente com um programa puro em JDK que você pode executar aqui mesmo.
Este capítulo pressupõe que você já conhece os fundamentos de testes abordados em Introdução ao JUnit 5 e Asserções com JUnit. O Mockito complementa o JUnit — o JUnit executa o teste e verifica valores, enquanto o Mockito fornece os colaboradores falsos.
Por que usar mocks
A classe sendo testada (o system under test, ou SUT) geralmente recebe seus colaboradores pelo construtor — é isso que a injeção de dependência proporciona. Em um teste, você passa um colaborador falso em vez do real. Um bom colaborador falso cumpre duas funções:
- Stubbing — retorna o valor que o cenário de teste precisa (
charge(...)retornatrue, ou lança uma exceção), para que você possa direcionar o SUT por um caminho específico sem uma chamada de rede real. - Verificação — registra cada chamada recebida, para que o teste possa afirmar que o SUT o chamou da maneira correta, o número correto de vezes, com os argumentos corretos.
O Mockito gera esse colaborador falso para qualquer interface ou classe não-final em tempo de execução, portanto você nunca precisa escrevê-lo manualmente. Mas entender o que ele gera torna a API óbvia.
Criando mocks e stubbing de retornos
Mockito.mock(Type.class) produz um mock. Por padrão, cada método retorna um valor vazio "amigável" — null para objetos, false para booleanos, 0 para números. Você então sobrescreve os métodos que importam com when(...).thenReturn(...).
import static org.mockito.Mockito.*;
PaymentGateway gateway = mock(PaymentGateway.class);
// Stub: when charge is called with these args, return true.
when(gateway.charge("acct-7", 1999)).thenReturn(true);
// Stub a method to throw, to test error handling.
when(gateway.charge("acct-x", 1)).thenThrow(new GatewayException("down"));Para métodos void, a ordem se inverte: doThrow(...).when(mock).method(). Os stubs também podem ser relaxados com correspondentes de argumentos como anyString() e anyInt() para que disparem em qualquer chamada, não apenas em um conjunto exato de argumentos.
Verificando interações
Após a execução do SUT, verify(...) afirma como o mock foi utilizado. É assim que você testa efeitos colaterais — um e-mail que deveria ter sido enviado, uma linha que deveria ter sido salva — sem inspecionar o sistema real.
verify(gateway).charge("acct-7", 1999); // called exactly once (default)
verify(gateway, times(2)).charge(anyString(), anyInt());
verify(gateway, never()).refund(anyString()); // must NOT have been called
verifyNoMoreInteractions(gateway); // nothing else happenedOs modos de verificação mais comuns:
| Modo | Significado |
|---|---|
times(n) | Chamado exatamente n vezes |
never() | Equivale a times(0) |
atLeastOnce() / atLeast(n) | Chamado pelo menos uma vez / n vezes |
atMost(n) | Chamado no máximo n vezes |
only() | Este foi o único método chamado no mock |
Capturando argumentos
Quando você precisa inspecionar o que foi passado — não apenas que uma chamada ocorreu — use um ArgumentCaptor. Ele captura o argumento real para que você possa afirmar sobre seus campos, o que é valioso quando o SUT constrói um objeto antes de passá-lo adiante.
ArgumentCaptor<Order> captor = ArgumentCaptor.forClass(Order.class);
verify(repository).save(captor.capture());
Order saved = captor.getValue();
assertEquals("acct-7", saved.account());
assertEquals(1999, saved.amountCents());@Mock, @InjectMocks e spies
Em classes de teste reais, raramente se chama mock() manualmente. As anotações cuidam de tudo: @Mock declara um campo mock, @InjectMocks constrói o SUT e injeta os mocks em seu construtor, e @ExtendWith(MockitoExtension.class) (JUnit 5) ativa o processamento.
@ExtendWith(MockitoExtension.class)
class CheckoutServiceTest {
@Mock PaymentGateway gateway;
@InjectMocks CheckoutService service; // gets the mock injected
@Test
void paysWhenGatewayApproves() {
when(gateway.charge("acct-7", 1999)).thenReturn(true);
assertEquals("PAID", service.checkout("acct-7", 1999));
verify(gateway).charge("acct-7", 1999);
}
}Um spy (spy(realObject)) é o meio-termo: envolve um objeto real e executa métodos reais, a menos que sejam stubados — útil para mocking parcial de código legado.
final, métodos final, métodos static ou métodos private. Se precisar fazer mock de uma classe final, habilite o MockMaker mockito-inline; caso contrário, refatore para usar uma interface.Quando não usar mocks
Mocks são poderosos, mas o uso excessivo produz testes que passam enquanto o código real está quebrado. Use um mock apenas quando o colaborador real for lento, não determinístico, tiver efeitos colaterais ou ainda não tiver sido construído. Não faça mock de objetos de valor, da própria classe sendo testada, ou de tipos que você não possui (encapsule uma API de terceiros em sua própria interface e faça mock dela). Quando o colaborador for simples e puro — uma calculadora simples, uma lista em memória — use o real e faça asserções diretamente sobre seu resultado.
Exemplo prático: um mock construído manualmente
O Mockito em si não está no classpath desta página, então o programa executável abaixo constrói o mock manualmente — uma pequena classe que implementa a interface de dependência, mantém um valor de retorno stubado e registra cada chamada. Essa é exatamente a maquinaria que o Mockito gera para você em tempo de execução, então entendê-la revela o que when/thenReturn e verify fazem por baixo dos panos.
O que observar na execução:
- O
stubbedResult = truedoMockGatewayé a forma manual dewhen(gateway.charge(...)).thenReturn(true); como o stub retornoutrue, o SUT imprimiuresult : PAIDsem que nenhum pagamento real tenha ocorrido. invocationCount == 1imprimindotrueé exatamente o queverify(gateway).charge(...)verifica — o mock contou que foi chamado uma vez, que é como o Mockito transforma "essa interação ocorreu?" em uma asserção de aprovação/reprovação.- A lista
callscapturoucharge(acct-7, 1999), a ideia de captura de argumento por trás doArgumentCaptor: um mock lembra não apenas que foi chamado, mas com quais argumentos, para que o teste possa afirmar sobre os argumentos reais. - Recriar o mock com
stubbedResult = falsedirecionou o SUT pelo outro ramo e imprimiudeclined result : DECLINED, mostrando como um único objeto falso permite simular todos os cenários que o colaborador real poderia produzir. - A cláusula de guarda retornou
INVALIDantes de chegar ao gateway, entãoinvocationCount == 0imprimiutrue— a prova executável deverify(gateway, never()).charge(...), afirmando que uma dependência foi deliberadamente não tocada.