W3docs

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(...) retorna true, 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 happened

Os modos de verificação mais comuns:

ModoSignificado
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.

Aviso
O Mockito só pode fazer mock do que é sobreponível. Por padrão, ele não consegue fazer mock de classes 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.

java— editable, runs on the server

O que observar na execução:

  • O stubbedResult = true do MockGateway é a forma manual de when(gateway.charge(...)).thenReturn(true); como o stub retornou true, o SUT imprimiu result : PAID sem que nenhum pagamento real tenha ocorrido.
  • invocationCount == 1 imprimindo true é exatamente o que verify(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 calls capturou charge(acct-7, 1999), a ideia de captura de argumento por trás do ArgumentCaptor: 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 = false direcionou o SUT pelo outro ramo e imprimiu declined 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 INVALID antes de chegar ao gateway, então invocationCount == 0 imprimiu true — a prova executável de verify(gateway, never()).charge(...), afirmando que uma dependência foi deliberadamente não tocada.

Prática

Prática
Em um teste unitário com Mockito, qual é o propósito de uma chamada como verify(gateway, never()).charge(anyString(), anyInt())?
Em um teste unitário com Mockito, qual é o propósito de uma chamada como verify(gateway, never()).charge(anyString(), anyInt())?
Was this page helpful?