Dominando o MockK, Parte 1: Mocks de Classes e Objetos
Ao concluir esta aula, seremos capazes de:
- Entender o que é o MockK e seus principais recursos nativos para Kotlin.
- Obter as dependências necessárias com Gradle (Kotlin DSL) e Maven.
- Criar dublês de teste (test doubles) com o MockK manualmente e com anotações.
- Compreender como integrar o MockK com o JUnit Jupiter 6.
Esta aula assume que:
- Você sabe executar testes Kotlin com Maven ou Gradle
- Você sabe escrever classes de teste com JUnit Jupiter e Kotlin
O Que É o MockK?
O MockK é uma biblioteca de mocking projetada especificamente para Kotlin. Suas principais características são:
- Suporta mocks, stubs e spies (espiões), além de classes
finaleobjectpor padrão. - Suporta tanto stubs/mocks estritos (strict) quanto relaxados (relaxed).
- Possui um suporte extremamente versátil para correspondência de argumentos (argument matching).
- Permite criar dublês de teste utilizando tanto a abordagem manual quanto anotações.
- Fornece suporte integrado para JUnit Jupiter (JUnit 5/6) e ecossistema Spring Boot.
A seguir, veremos como obter as dependências necessárias com Maven e Gradle.
Obtendo as Dependências Necessárias
Ao utilizar Kotlin 2.4.21 e Coroutines modernas no ecossistema do Spring Boot 4.1.2, declaramos a dependência mockk na versão 1.14.0 (família MockK 1.14) em nosso script de build.
Se estivermos utilizando o Maven, podemos obter essa dependência adicionando a seguinte declaração à seção dependencies do nosso arquivo pom.xml:
|
|
Se estivermos utilizando o Gradle 9.8.2 (com Kotlin DSL), devemos adicionar a dependência mockk à configuração testImplementation. Podemos fazer isso adicionando a seguinte linha ao bloco dependencies do nosso arquivo build.gradle.kts:
|
|
Nota Histórica e Compatibilidade:
Nas versões iniciais do MockK (à época do Kotlin 1.2 e 1.3), existiam artefatos específicos por versão da linguagem (como
1.9.3.kotlin12). No MockK 1.14 com Kotlin 2.4.21 e JDK 21+, o artefato padrãoio.mockk:mockk:1.14.0resolve automaticamente a variante JVM compatível com o compilador K2 e com o JUnit Platform 6.
Vamos avançar e descobrir como podemos criar nossos dublês de teste utilizando a abordagem manual.
Criando Dublês de Teste com a Abordagem Manual
Antes de continuarmos, vale destacar que o MockK não diferencia estruturalmente stubs e mocks no momento da instanciação. Isso significa que, se quisermos criar tanto um stub quanto um mock, utilizamos a mesma função chamada mockk().
Embora o nome dessa função sugira que o objeto criado seja sempre um mock, utilizamos a função mockk() para ambos os papéis porque isso mantém a API simples e concisa.
A seguir, criaremos stubs, mocks e spies com o MockK. Observe que todos os exemplos a seguir substituem uma lista (List / MutableList) ou um objeto pelo dublê de teste criado.
Criando Stubs
Se quisermos criar um stub estrito (strict stub), devemos seguir estes passos:
- Adicionar uma propriedade
lateinità nossa classe de teste e garantir que essa propriedade armazene uma lista de objetosLong(List<Long>). - Adicionar uma função de configuração (setup) à nossa classe de teste e garantir que ela seja invocada antes da execução de cada função de teste (
@BeforeEach). Criaremos o nosso stub dentro desta função. - Criar o nosso stub invocando a função
mockk()e armazenar o stub criado na propriedadelist. Ao invocar a funçãomockk(), devemos configurar o tipo do stub criado via parâmetro de tipo genérico.
Após criarmos um stub estrito, o código-fonte da nossa classe de teste ficará da seguinte forma:
|
|
Se quisermos criar um stub relaxado (relaxed stub), devemos seguir estes passos:
- Adicionar uma propriedade
lateinità nossa classe de teste e garantir que essa propriedade contenha uma lista de objetos (List<*>). Utilizamos a projeção estrela (star projection) aqui porque o stubbing relaxado automático precisa evitar armadilhas de apagamento de tipo (type erasure) ou ClassCastException ao sintetizar valores de retorno genéricos não configurados. - Adicionar uma função de configuração (setup) à nossa classe de teste e garantir que essa função seja invocada antes da execução de cada teste. Criaremos o nosso stub nesta função.
- Criar o nosso stub invocando a função
mockk()e armazenar o stub criado na propriedadelist. Ao invocar a funçãomockk(), configuramos o tipo do stub criado e definimos o valor do parâmetro nomeadorelaxedcomotrue.
Após criarmos um stub relaxado, o código-fonte da nossa classe de teste ficará da seguinte forma:
|
|
Leitura Adicional:
Vamos avançar e descobrir como podemos criar mocks com o MockK.
Criando Mocks
Se quisermos criar um mock estrito (strict mock), devemos seguir estes passos:
- Adicionar uma propriedade
lateinità nossa classe de teste e garantir que essa propriedade contenha uma lista de objetosLong(List<Long>). - Adicionar uma função de configuração (setup) à nossa classe de teste e garantir que ela seja invocada antes da execução de cada função de teste. Criaremos o nosso mock dentro desta função.
- Criar o nosso mock invocando a função
mockk()e armazenar o mock criado na propriedadelist. Ao invocar a funçãomockk(), devemos configurar o tipo do mock criado.
Após criarmos um mock estrito, o código-fonte da nossa classe de teste ficará da seguinte forma:
|
|
Se quisermos criar um mock relaxado (relaxed mock), devemos seguir estes passos:
- Adicionar uma propriedade
lateinità nossa classe de teste e garantir que essa propriedade contenha uma lista de objetosLong(List<Long>). Observe que não precisamos obrigatoriamente usar a projeção estrela (*) aqui, desde que não dependamos do retorno automático de funções genéricas não configuradas. - Adicionar uma função de configuração (setup) à nossa classe de teste e garantir que ela seja invocada antes da execução de cada função de teste. Criaremos o nosso mock nesta função.
- Criar o nosso mock invocando a função
mockk()e armazenar o mock criado na propriedadelist. Ao invocar a funçãomockk(), devemos configurar o tipo do mock criado e definir o valor do parâmetrorelaxedcomotrue.
Após criarmos um mock relaxado, o código-fonte da nossa classe de teste ficará da seguinte forma:
|
|
Se quisermos fazer mock ou stub de objetos singleton do Kotlin (object), devemos seguir estes passos:
- Adicionar uma função de configuração (setup) à nossa classe de teste e garantir que essa função seja invocada antes da execução de cada função de teste (
@BeforeEach). Criaremos o nosso mock ou stub nesta função. - Substituir o
objectKotlin pelo nosso dublê de teste. Podemos fazer isso invocando a funçãomockkObject(). Ao invocar essa função, devemos especificar o objeto Kotlin que será transformado em dublê de teste. - Adicionar uma função de limpeza (teardown) à nossa classe de teste e garantir que essa função seja invocada após a execução de cada função de teste (
@AfterEach). Destruiremos o nosso dublê de teste nesta função para restaurar o estado original do singleton. - Destruir o nosso dublê de teste invocando a função
unmockkObject(). Ao invocar essa função, devemos especificar o objeto Kotlin que havia sido substituído pelo dublê de teste.
Após substituirmos um objeto Calculator por um mock, o código-fonte da nossa classe de teste ficará da seguinte forma:
|
|
Há dois pontos importantes que quero destacar:
- Podemos usar exatamente esta mesma técnica se quisermos substituir um
objectKotlin por um stub.- Se tivermos substituído múltiplos objetos Kotlin por dublês de teste, também podemos destruir todos os nossos dublês de uma só vez invocando a função
unmockkAll().Leitura Adicional:
A seguir, aprenderemos a criar spies (espiões) com o MockK.
Criando Spies
Se quisermos criar um spy, devemos seguir estes passos:
- Adicionar uma propriedade
lateinità nossa classe de teste e garantir que essa propriedade contenha uma lista mutável de objetosLong(MutableList<Long>). - Adicionar uma função de configuração (setup) à nossa classe de teste e garantir que ela seja invocada antes da execução de cada função de teste. Criaremos o nosso spy nesta função.
- Criar o nosso spy invocando a função
spyk()e armazenar o spy criado na propriedadelist. Ao invocar a funçãospyk(), devemos configurar o tipo concreto do spy criado (por exemplo,ArrayList<Long>).
Após criarmos um spy, o código-fonte da nossa classe de teste ficará da seguinte forma:
|
|
Leitura Adicional:
Agora já sabemos criar diferentes dublês de teste usando a abordagem manual. Vamos avançar e descobrir como podemos criar nossos dublês de teste utilizando as anotações do MockK.
Criando Dublês de Teste com Anotações
Quando queremos criar dublês de teste utilizando as anotações do MockK, podemos recorrer às seguintes anotações:
- A anotação
@MockKnos permite criar tanto stubs quanto mocks estritos e relaxados. Se quisermos criar um stub ou mock relaxado com ela, devemos definir o valor do atributorelaxedda anotação@MockKcomotrue. - A anotação
@RelaxedMockKnos permite criar stubs e mocks relaxados diretamente. - A anotação
@SpyKnos permite criar spies envolvendo uma instância existente.
A seguir, criaremos diferentes dublês de teste utilizando essas anotações. Podemos criar nossos dublês de teste seguindo estes passos:
- Adicionar uma propriedade
lateinità nossa classe de teste anotada com@MockKe garantir que ela contenha um mock estrito. - Adicionar uma propriedade
lateinità nossa classe de teste anotada com@MockK(relaxed = true)e garantir que ela contenha um mock relaxado. - Adicionar uma nova propriedade inicializada à nossa classe de teste anotada com
@SpyKe garantir que ela contenha um spy. - Adicionar uma propriedade
lateinità nossa classe de teste anotada com@MockKe garantir que ela contenha um stub estrito. - Adicionar uma propriedade
lateinità nossa classe de teste anotada com@RelaxedMockKe garantir que ela contenha um stub relaxado. - Adicionar uma função de configuração (setup) à nossa classe de teste e garantir que essa função seja invocada antes da execução de cada função de teste. Inicializaremos nossos dublês de teste nesta função.
- Criar nossos dublês de teste invocando a função
MockKAnnotations.init(this). Ao invocar essa função, devemos passar a instância de teste (this) que contém os campos ou propriedades onde nossos dublês de teste serão injetados.
Após escrevermos uma classe de teste que cria nossos dublês de teste com as anotações do MockK, seu código-fonte ficará da seguinte forma:
|
|
Antes de continuarmos, quero ressaltar que não sou um grande fã do uso de anotações para essa finalidade, pois elas tornam nossa vida “fácil demais”. Os desenvolvedores de diferentes bibliotecas de mocking adicionaram suporte a anotações porque os usuários reclamavam que criar dublês de teste manualmente exigia muito código.
O problema é que as anotações ajudam a esconder problemas de design presentes no nosso código. Acredito que, se configurar o sistema sob teste (System Under Test - SUT) for trabalhoso demais manualmente, é um sinal claro de que o sistema sob teste possui dependências em excesso e precisa ser refatorado. É por isso que recomendo preferir a criação explícita de dublês de teste em vez de anotações.
Leitura Adicional:
A seguir, veremos como podemos criar nossos dublês de teste usando anotações integradas diretamente ao modelo de extensões do JUnit Jupiter (JUnit 5 / JUnit 6).
Integrando o MockK com o JUnit Jupiter (JUnit 5 / 6)
Se estivermos utilizando o JUnit Jupiter 6.0.3 (cuja API de extensão mantém o pacote org.junit.jupiter.api.extension introduzido no JUnit 5) e quisermos criar nossos dublês de teste usando as anotações do MockK, podemos delegar a inicialização para a extensão MockKExtension. Para usar essa extensão, devemos fazer as seguintes alterações na classe de teste que criamos na seção anterior:
- Remover a função de configuração (
@BeforeEach) da nossa classe de teste. - Garantir que a
MockKExtensionseja invocada quando executarmos os testes da nossa classe anotando-a com@ExtendWith(MockKExtension::class).
Após fazermos essas alterações em nossa classe de teste, seu código-fonte ficará da seguinte forma:
|
|
Leitura Adicional:
Agora sabemos como criar nossos dublês de teste com o MockK e como integrá-lo ao JUnit Jupiter. Vamos resumir o que aprendemos nesta aula.
Resumo
Esta aula nos ensinou quatro conceitos fundamentais:
- Podemos criar mocks e stubs utilizando a função
mockk(), a anotação@MockKou a anotação@RelaxedMockK. - Se quisermos fazer mock de objetos singleton do Kotlin (
object), devemos utilizar as funçõesmockkObject()eunmockkObject()(ouunmockkAll()). - Podemos criar spies utilizando a função
spyk()ou a anotação@SpyK. - Se quisermos criar nossos dublês de teste com anotações, devemos inicializá-los utilizando a função
MockKAnnotations.init(this)ou registrando a extensãoMockKExtensionno JUnit Jupiter.