O que é?

Mockito é um framework que facilita a criação de mocks - objetos falsos que substituem partes reais do código.

Para aprender mais sobre mocks e seu papel nos testes confira o artigo Mocks: Introdução a Automatização de Testes com Mock Object.

Por que é útil?

Mockito melhora o código de teste, porque torna ele mais legível. Além disso, Mockito também produz mensagens que facilitam rastrear erros.

Características

  • Primeiramente escrito para Java, mas foi portado para Kotlin;
  • Torna o código mais legível;
  • Gera mensagens que facilitam rastrear erros.

Como utilizar: configuração

Configure Mockito em um projeto usando o Gradle, adicionando na sessão dependencies do arquivo build.gradle.kts as seguintes dependências do Código 1.


  testImplementation("org.mockito.kotlin:mockito-kotlin:4.0.0")
  testImplementation("org.junit.jupiter:junit-jupiter-api:5.3.1")
  testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.3.1")
  
Código 1. Instalando o Mockito no Gradle

Note que você ainda precisará do JUnit para usar o Mockito.

Após isso, caso seu projeto ainda não tenha uma sessão de testes configurada, insira uma task, ainda no arquivo build.gradle.kts, conforme o Código 2.


  tasks.withType<Test> {
      useJUnitPlatform()
  }
  
Código 2. Configuração de testes

A partir disso podemos começar a escrever os primeiros mocks com o Mockito.

Exemplo prático

Para ilustrar como criar um teste usando o Mockito criaremos um pequeno projeto com duas classes, conforme mostra a Figura 1. A ideia por trás desse microprojeto é simular um pagamento.

Classes PaymentGateway e Payment
Figura 1. Classes PaymentGateway e Payment

Payment representa o pagamento e PaymentGateway a comunicação entre a aplicação e a empresa que debita o saldo do cartão de crédito do cliente.

Código das classes

A função das classes Payment e PaymentGateway importam mais que seus códigos no contexto deste artigo.

Entendendo o que elas fazem podemos ter uma ideia de como elas fazem.

Sendo assim, os Códigos 3 e 4 demonstram a resposta de seus métodos em pseudocódigo.


  open class PaymentGateway {
   
      open fun request() = "Request payment gateway"
  }
  
Código 3. Classe PaymentGateway

  class Payment(private val paymentGateway: PaymentGateway) {
   
      fun pay() = {
          // Calcula o valor final
   
          // Aplica descontos
   
          paymentGateway.request()
      }
  }
  
Código 4. Classe Payment

Relação entre Payment e PaymentGateway

O objetivo desse exemplo é apresentar uma classe que contém regras de negócio, Payment, que depende de outra que não possui, PaymentGateway.

Uma vez que PaymentGateway pode falhar, por uma queda na internet, por exemplo, precisamos isolá-la ao testar Payment. Para isso criamos um mock para PaymentGateway. Assim, caso Payment falhe isso não ocorrerá ao acaso.

Criando e validando o mock

Antes de criar um mock precisamos criar uma classe de testes , como mostra o Código 5. No método de teste `Succeeds to pay given a valid payment gateway` inserimos o código de teste.


  internal class PaymentTest {
   
      @Test
      fun `Succeeds to pay given a valid payment gateway`() {
          val mock = mock<PaymentGateway> {
              on { request() } doReturn "mock implementation"
          }
   
          val payment = Payment(mock)
          payment.pay()
   
          verify(mock).request()
      }
  }
  
Código 5. Classe de teste

Na Linha 5 criamos o mock usando o método mock do Mockito.

Na Linha 6, estamos substituindo o código real do método PaymentGateway::request() por um código falso. Ele vai fazer com que a string "mock implementation" seja retornada sempre que esse método for chamado.

A instrução on { request() } doReturn "mock implementation", na Linha 6, pode ser lida como "para o método request() quando ele for invocado, retorne 'mock implementation'".

A técnica de criar código falso para os mocks é chamada stubbing.

Na Linha 9 passamos o mock para o construtor de Payment.

Na Linha 10, invocamos o método Payment::pay() do Código 4. Internamente, o método Payment::pay() deve invocar PaymentGateway::request(), fazendo com nosso stub seja executado.

Na Linha 12 fazemos o assertion, a verificação que determina se o teste falhou ou não. Nessa assertion usamos Mockito::verify() para checar se o método PaymentGateway::request() foi invocado, como mostra a Figura 2.

Teste bem-sucedido
Figura 2. Teste bem-sucedido

Nesse caso, uma vez que PaymentGateway::request() foi invocado, o teste foi bem-sucedido.

Produzindo e analisando uma falha

Caso se deseje ver o teste falhar, podemos comentar a invocação de PaymentGateway::request() em Payment::pay(), conforme o Código 6.


  class Payment(private val paymentGateway: PaymentGateway) {
   
    fun pay() {
       //    paymentGateway.request()
    }
  }
  
Código 6. PaymentGateway::request() em Payment comentado

Ao fazermos isso, Mockito::verify() saberá que PaymentGateway::request() em Payment não foi invocado, pois está comentado, e falhará o teste com uma mensagem como a apresentada no Código 7 e na Figura 3.


  Wanted but not invoked:
  paymentGateway.request();
  -> at PaymentTest.Test mock(PaymentTest.kt:19)
  Actually, there were zero interactions with this mock.
  
Código 7. Mensagem de erro gerada pelo Mockito
Teste mal-sucedido
Figura 3. Teste mal-sucedido

A mensagem nos permite rastrear o erro, uma vez que mostra onde o problema ocorreu, PaymentTest.kt:19", e o que o ocasionou, "Wanted but not invoked: paymentGateway.request();".

Final classes

Anteriormente, usamos o modificador open na classe PaymentGateway apresentado no Código 8.


  open class PaymentGateway {
      …
  }
  
Código 8. Classe PaymentGateway com o modificar open

Mockito não funciona com classes final, padrão do Kotlin, sem alguma configuração.

Se tentarmos executar novamente o teste após remover open da classe PaymentGateway receberemos o erro apresentado no Código 9.


  Cannot mock/spy class PaymentGateway
  Mockito cannot mock/spy because :
   - final class
  org.mockito.exceptions.base.MockitoException: 
  Cannot mock/spy class PaymentGateway
  Mockito cannot mock/spy because :
   - final class
  
Código 9. Erro exibido ao remover open de PaymentGateway

Além de usar open para contornar esse problema, podemos configurar o Mockito de forma que ele saiba o que fazer nessa condição.

Para isso crie uma arquivo chamado org.mockito.plugins.MockMaker na pasta src/test/resources/mockito-extensions com conteúdo do Código 10.


  mock-maker-inline
  
Código 10. Configuração no arquivo org.mockito.plugins.MockMaker

A partir disso podemos remover o modificador open da classe PaymentGateway e nosso código continuará funcionando conforme esperado.

Conclusão

Atualmente, escrevemos mais códigos de testes do que qualquer outro em aplicações. Por este motivo, dominar técnicas triviais de teste como mocking e stubbing é fundamental para o programador. Nesse artigo você aprendeu como usar o Mockito para essa tarefa, escrevendo código de testes mais limpo e que produzem mensagens mais eficientes quanto a rastreabilidade de erros.