Usando Spies e Objetos Parciais em Kotest
Depois de concluirmos esta aula, nós:
- Saberemos como obter as dependências necessárias para usar o MockK com Kotest no Gradle e no Maven.
- Saberemos como criar spies (
spyk) envolvendo objetos reais no MockK. - Compreenderemos como verificar as interações que ocorreram entre o sistema sob especificação e um spy.
- Saberemos como sobrescrever (stub) métodos específicos de um spy sem executar o método real subjacente.
1. Conhecendo o Sistema Sob Especificação
Durante esta aula, criaremos um spy que envolve uma implementação real da interface MessageRepository chamada MessageRepositoryImpl e usaremos esse spy para especificar o comportamento da classe MessageService.
A classe MessageRepositoryImpl implementa dois métodos que registram logs via SLF4J (o que nos ajuda a comprovar visualmente quando o método real foi ou não executado):
- O método
findById(id: Long?)cria um novo objetoMessagecom oidrecebido e o texto"Hello from Spy!", retornandoOptional.of(found). - O método
save(message: Message)definemessage.id = 3Le retorna o objetomessage.
Veja o código-fonte de MessageRepositoryImpl em Kotlin:
|
|
2. Obtendo as Dependências Necessárias
No Spock Framework, para criar spies (ou mocks de classes concretas), era necessário adicionar manualmente byte-buddy ou cglib-nodep ao classpath.
Com o MockK 1.14, o Byte Buddy já é gerenciado internamente pelo próprio artefato io.mockk:mockk-jvm, inclusive suportando classes final nativas do Kotlin sem exigir o plugin kotlin-allopen!
Se estivermos usando Maven, declaramos io.mockk:mockk-jvm no pom.xml:
|
|
Se estivermos usando Gradle 9.8.2 (Kotlin DSL), adicionamos io.mockk:mockk no bloco dependencies:
|
|
Vamos ver agora como criar um spy com MockK dentro da nossa especificação do Kotest.
3. Criando Spies com MockK (spyk)
Podemos criar um spy com o MockK utilizando a função spyk() de duas formas principais:
spyk<MessageRepositoryImpl>(): sem passar argumentos, o MockK instancia um novo objeto chamando o construtor padrão sem argumentos da classeMessageRepositoryImple o envolve em um spy.spyk(MessageRepositoryImpl(...)): passando uma instância já criada como argumento. Essa opção é excelente quando a classe possui argumentos de construtor ou quando recebemos um objeto já instanciado.
Vamos configurar nossa especificação MessageServiceSpySpec criando um spy de MessageRepositoryImpl e injetando-o em MessageService:
|
|
Quando criamos um spy com spyk(...), o MockK cria um wrapper com a mesma API do objeto real que delega todas as chamadas de método ao objeto real por padrão. Em outras palavras:
- O sistema sob especificação invoca um método do spy.
- O spy grava a chamada no histórico de interações do MockK.
- O spy invoca o método real de
MessageRepositoryImple devolve o valor retornado pelo objeto real.
4. Verificando Interações em um Spy
Para verificar as interações ocorridas entre o sistema sob especificação e um spy, utilizamos exatamente o mesmo bloco verify(...) que aprendemos na aula anterior sobre mocks!
Vamos verificar que o método save() do spy MessageRepositoryImpl foi invocado uma vez quando criamos uma nova mensagem e que o objeto Message passado tinha o texto "Hello World!":
|
|
Ao executar esse teste, veremos no console a linha de log Saving a message with information: Message(id=null, messageText=Hello World!) emitida por MessageRepositoryImpl, comprovando que o método real foi de fato executado!
5. Sobrescrevendo (Stubbing) o Comportamento de um Método do Spy
Se quisermos substituir o comportamento de um método específico de um spy (um “mock parcial”), precisamos ter em mente três pontos:
- Quando configuramos um método de um spy com
every { repository.save(...) } answers { ... }, o método do objeto real NÃO é invocado quando o sistema sob especificação chama aquele método! - Diferentemente do Mockito clássico (onde
when(spy.save(...))executava o método real durante a configuração e exigiadoReturn(...).when(spy)), no MockK o blocoevery { repository.save(...) }intercepta a chamada com segurança sem invocar o método real! - Usar spies como mocks parciais é uma técnica poderosa mas controversa: ela pode ser útil ao lidar com código legado difícil de refatorar, mas em projetos novos (greenfield) devemos sempre preferir composição limpa com mocks/stubs puros.
Vamos escrever um teste onde sobrescrevemos o método save() do nosso spy MessageRepositoryImpl para atribuir id = 1L (em vez do 3L que o método real atribuiria) e verificamos que a interação ocorreu uma vez:
|
|
Quando executamos esse segundo cenário, nenhuma mensagem de log do MessageRepositoryImpl.save() é impressa no console e returned.id é 1L, provando que o método real foi suprimido pelo nosso stub!
Resumo
Nesta aula que encerra o Tópico 02 do Capítulo 03, aprendemos cinco pontos fundamentais:
- O artefato
io.mockk:mockkjá traz todo o suporte de instrumentação necessário para criar spies de classes Kotlin (mesmofinal). - Criamos um spy usando a função
spyk(objetoReal)ouspyk<ClasseReal>(). - Por padrão, qualquer chamada a um método de um spy é registrada pelo MockK e repassada para o método correspondente do objeto real.
- Se configurarmos um método do spy com
every { ... }, o método real não será executado quando o sistema sob especificação o invocar. - Verificamos as interações no spy usando
verify(exactly = 1) { ... }dentro do blocoThenda nossa especificação do Kotest.