Rafael

Engenheiro de Software

Olá, Eu sou Rafael. Estou construindo coisas para a web e mobile.

Um desenvolvedor dedicado a criar experiências digitais de alto desempenho, acessíveis e limpas. Eu me especializo em transformar problemas complexos em soluções de engenharia elegantes.

Tutorial: mockkStatic e slot em testes unitários com Kotlin + Spring Boot

Quando começamos a escrever testes unitários em Kotlin utilizando MockK, dois recursos aparecem com bastante frequência:

  • slot → captura argumentos enviados para um método.
  • mockkStatic → faz mock de métodos estáticos (ou funções de alto nível do Kotlin).

1. O que é um Slot?

Imagine que você quer testar não apenas se um método foi chamado, mas qual objeto foi enviado para ele.

KOTLIN
fun create(purchase: PurchaseModel) {
    purchaseRepository.save(purchase)
    applicationEventPublisher.publishEvent(
        PurchaseEvent(this, purchase)
    )
}

O teste precisa garantir que:

  • salvou a compra;
  • publicou um evento;
  • o evento contém a compra correta.

É aí que entra o slot.

Criando um Slot:

KOTLIN
private var purchaseEventSlot = slot<PurchaseEvent>()

Capturando o argumento

Ao verificar a chamada:

KOTLIN
verify {
    applicationEventPublisher.publishEvent(
        capture(purchaseEventSlot)
    )
}

O capture() diz ao MockK:

“Toda vez que esse método for chamado, coloque o argumento dentro desse Slot.”

Depois disso podemos acessar. que retorna exatamente o objeto enviado.

KOTLIN
assertEquals(
    purchase,
    purchaseEventSlot.captured.purchaseModel
)

Exemplo de uma classe completa com slot:

KOTLIN
package mercado_livro.service

import io.mockk.*
import io.mockk.impl.annotations.InjectMockKs
import io.mockk.impl.annotations.MockK
import io.mockk.junit5.MockKExtension
import mercado_livro.events.PurchaseEvent
import mercado_livro.helper.buildPurchase
import mercado_livro.repository.PurchaseRepository
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.context.ApplicationEventPublisher
import kotlin.test.assertEquals

@ExtendWith(MockKExtension::class)
class PurchaseServiceTest {
    @MockK
    private lateinit var purchaseRepository: PurchaseRepository

    @MockK
    private lateinit var applicationEventPublisher: ApplicationEventPublisher

    @InjectMockKs
    private lateinit var purchaseService: PurchaseService

    private var purchaseEvntSlot = slot<PurchaseEvent>()

    @Test
    fun `should create purchase and publish event`(){
        val purchase = buildPurchase()

        every { purchaseRepository.save(purchase) } returns  purchase
        every { applicationEventPublisher.publishEvent(any()) } just runs

        purchaseService.create(purchase)

        verify(exactly = 1) { purchaseRepository.save(purchase)  }
        verify(exactly = 1) { applicationEventPublisher.publishEvent(capture(purchaseEvntSlot)) }

        assertEquals(purchase,purchaseEvntSlot.captured.purchaseModel)
    }

}

O que é mockkStatic?

Em dados que mudam toda vez como em UUID. O problema é que toda execução gera um valor diferente. Isso torna o teste imprevisível.

KOTLIN
val nfe = UUID.randomUUID().toString()

Como o UUID muda toda vez, não conseguimos fazer:

KOTLIN
assertEquals(expected, generated)

Solução

Transformamos o método estático em um mock.

KOTLIN
mockkStatic(UUID::class)

Agora o MockK intercepta qualquer chamada para:

KOTLIN
UUID.randomUUID()

Vamos ver uma classe de MockKStatic completa de testes:

KOTLIN
package mercado_livro.events.listener

import io.mockk.*
import io.mockk.impl.annotations.InjectMockKs
import io.mockk.impl.annotations.MockK
import io.mockk.junit5.MockKExtension
import mercado_livro.events.PurchaseEvent
import mercado_livro.helper.buildPurchase
import mercado_livro.service.PurchaseService
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import java.util.*

@ExtendWith(MockKExtension::class)
class GenerateNfeListenerTest{

    @MockK
    private lateinit var purchaseService: PurchaseService
    @InjectMockKs
    private lateinit var generateNfeListener: GenerateNfeListener

    @Test
    fun `should generate nfe`(){
        val purchase= buildPurchase(nfe = null)
        val fakeNfe = UUID.randomUUID()
        val purchaseExpected= purchase.copy(nfe = fakeNfe.toString())
        mockkStatic(UUID::class)

        every { UUID.randomUUID() } returns fakeNfe
        every { purchaseService.update(purchaseExpected) } just runs
        generateNfeListener.listener(PurchaseEvent(this,purchase))

        verify(exactly = 1) { purchaseService.update(purchaseExpected)  }
    }

}

Para ver testes de integração, clique aqui.

0 comentários

Deixe seu comentário