---
title: "Testes em Kotlin com JUnit 5 e MockK — Guia Prático | Kotlin Brasil"
url: "https://kotlin.dev.br/blog/kotlin-testes-junit5-mockk-guia/"
markdown_url: "https://kotlin.dev.br/blog/kotlin-testes-junit5-mockk-guia.MD"
description: "Aprenda a escrever testes unitários em Kotlin com JUnit 5 e MockK. Guia prático com mocking, coroutines, boas práticas e exemplos reais."
date: "2026-03-25"
author: "Karina Melo"
---

# Testes em Kotlin com JUnit 5 e MockK — Guia Prático | Kotlin Brasil

Aprenda a escrever testes unitários em Kotlin com JUnit 5 e MockK. Guia prático com mocking, coroutines, boas práticas e exemplos reais.


Testar código em Kotlin é uma experiência completamente diferente de testar em Java. A linguagem oferece recursos como data classes, extension functions, coroutines e null safety que mudam a forma como escrevemos — e testamos — nosso código. Neste guia prático, vamos explorar como usar **JUnit 5** e **MockK** para escrever testes unitários idiomáticos em Kotlin.

## Por que JUnit 5 + MockK?

No ecossistema Java, a combinação clássica é JUnit + Mockito. Funciona em Kotlin? Sim, mas com ressalvas. Mockito tem dificuldades com classes `final` (que são o padrão em Kotlin), exige workarounds com `mockito-kotlin` e não suporta nativamente coroutines.

**MockK** foi criado especificamente para Kotlin. Ele entende classes finais, oferece uma DSL fluente e idiomática, e suporta coroutines, relaxed mocks e muito mais — tudo de forma nativa.

Já o **JUnit 5** (Jupiter) trouxe melhorias significativas sobre o JUnit 4: testes parametrizados mais poderosos, extensões modulares, lifecycle hooks e suporte a Kotlin muito mais robusto.

## Configurando o projeto

Adicione as dependências no seu `build.gradle.kts`:

```kotlin
dependencies {
    testImplementation("org.junit.jupiter:junit-jupiter:5.10.2")
    testImplementation("io.mockk:mockk:1.13.10")
    testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0")
}

tasks.test {
    useJUnitPlatform()
}
```

Com essas três dependências, você tem tudo o que precisa: JUnit 5 como framework de teste, MockK para mocking e a biblioteca de testes de coroutines.

## Escrevendo seu primeiro teste

Vamos começar com um exemplo simples. Imagine um serviço que calcula descontos:

```kotlin
class DescontoService {
    fun calcularDesconto(preco: Double, percentual: Int): Double {
        require(percentual in 0..100) { "Percentual deve estar entre 0 e 100" }
        return preco * (1 - percentual / 100.0)
    }
}
```

O teste com JUnit 5 fica assim:

```kotlin
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows

class DescontoServiceTest {

    private val service = DescontoService()

    @Test
    fun `deve aplicar desconto de 20 por cento`() {
        val resultado = service.calcularDesconto(100.0, 20)
        assertEquals(80.0, resultado, 0.01)
    }

    @Test
    fun `deve lancar excecao para percentual invalido`() {
        assertThrows<IllegalArgumentException> {
            service.calcularDesconto(100.0, 150)
        }
    }
}
```

Note o uso de backticks nos nomes dos testes — um recurso do Kotlin que permite nomes descritivos e legíveis. Esse é um dos grandes diferenciais ao testar em Kotlin versus Java.

## Mocking com MockK — O básico

O poder do MockK aparece quando você precisa isolar dependências. Vamos considerar um repositório e um serviço:

```kotlin
interface UsuarioRepository {
    fun buscarPorId(id: Long): Usuario?
    fun salvar(usuario: Usuario): Usuario
}

data class Usuario(val id: Long, val nome: String, val email: String)

class UsuarioService(private val repository: UsuarioRepository) {
    fun obterUsuario(id: Long): Usuario {
        return repository.buscarPorId(id)
            ?: throw NoSuchElementException("Usuário não encontrado")
    }
}
```

Com MockK, o teste fica extremamente limpo:

```kotlin
import io.mockk.*
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import kotlin.test.assertEquals

class UsuarioServiceTest {

    private val repository = mockk<UsuarioRepository>()
    private val service = UsuarioService(repository)

    @Test
    fun `deve retornar usuario quando encontrado`() {
        val usuario = Usuario(1L, "Maria", "maria@email.com")
        every { repository.buscarPorId(1L) } returns usuario

        val resultado = service.obterUsuario(1L)

        assertEquals("Maria", resultado.nome)
        verify(exactly = 1) { repository.buscarPorId(1L) }
    }

    @Test
    fun `deve lancar excecao quando usuario nao encontrado`() {
        every { repository.buscarPorId(any()) } returns null

        assertThrows<NoSuchElementException> {
            service.obterUsuario(99L)
        }
    }
}
```

A sintaxe `every { ... } returns ...` é a DSL do MockK. É declarativa, fácil de ler e não exige casts estranhos como acontece com Mockito em Kotlin.

## Capturando argumentos com Slot

Às vezes, você precisa inspecionar o que foi passado para um mock. O `slot` do MockK resolve isso:

```kotlin
@Test
fun `deve salvar usuario com nome formatado`() {
    val slot = slot<Usuario>()
    every { repository.salvar(capture(slot)) } answers { slot.captured }

    val service = UsuarioService(repository)
    service.criarUsuario("joão silva", "joao@email.com")

    assertEquals("João Silva", slot.captured.nome)
}
```

Isso é especialmente útil quando seu serviço transforma dados antes de salvar — você captura exatamente o que foi enviado ao repositório.

## Testando coroutines com coEvery

Kotlin brilha em código assíncrono com [coroutines](/blog/coroutines-kotlin/), e o MockK suporta isso nativamente com `coEvery` e `coVerify`:

```kotlin
interface NotificacaoService {
    suspend fun enviar(usuarioId: Long, mensagem: String): Boolean
}

class PedidoService(
    private val repository: UsuarioRepository,
    private val notificacao: NotificacaoService
) {
    suspend fun processarPedido(usuarioId: Long) {
        val usuario = repository.buscarPorId(usuarioId)
            ?: throw NoSuchElementException("Usuário não encontrado")
        notificacao.enviar(usuarioId, "Pedido processado, ${usuario.nome}!")
    }
}
```

O teste usa `runTest` da biblioteca `kotlinx-coroutines-test`:

```kotlin
import kotlinx.coroutines.test.runTest
import io.mockk.*
import org.junit.jupiter.api.Test

class PedidoServiceTest {

    private val repository = mockk<UsuarioRepository>()
    private val notificacao = mockk<NotificacaoService>()
    private val service = PedidoService(repository, notificacao)

    @Test
    fun `deve enviar notificacao ao processar pedido`() = runTest {
        val usuario = Usuario(1L, "Carlos", "carlos@email.com")
        every { repository.buscarPorId(1L) } returns usuario
        coEvery { notificacao.enviar(any(), any()) } returns true

        service.processarPedido(1L)

        coVerify { notificacao.enviar(1L, "Pedido processado, Carlos!") }
    }
}
```

O `coEvery` e `coVerify` são as versões suspending dos `every` e `verify` tradicionais. Sem eles, testar código com coroutines seria muito mais trabalhoso.

## Relaxed mocks e spy

Nem sempre você quer configurar cada chamada de um mock. O **relaxed mock** retorna valores padrão para qualquer chamada não configurada:

```kotlin
val repository = mockk<UsuarioRepository>(relaxed = true)
// repository.buscarPorId(1L) retorna null (valor padrão para tipo nullable)
// repository.salvar(usuario) retorna um Usuario com valores padrão
```

Já o **spy** permite mockar parcialmente um objeto real:

```kotlin
val service = spyk(UsuarioService(repository))
every { service.validarEmail(any()) } returns true
// Outras funções continuam com a implementação real
```

## Boas práticas para testes em Kotlin

### 1. Use nomes descritivos com backticks

```kotlin
@Test
fun `deve calcular frete grátis para compras acima de R$200`() { }
```

### 2. Organize com @Nested

O JUnit 5 permite agrupar testes relacionados com `@Nested`:

```kotlin
class CarrinhoServiceTest {
    @Nested
    inner class `Ao adicionar item` {
        @Test
        fun `deve atualizar o total`() { }

        @Test
        fun `deve incrementar a quantidade`() { }
    }

    @Nested
    inner class `Ao remover item` {
        @Test
        fun `deve recalcular o total`() { }
    }
}
```

### 3. Use testes parametrizados

```kotlin
@ParameterizedTest
@CsvSource("100.0, 10, 90.0", "200.0, 50, 100.0", "50.0, 0, 50.0")
fun `deve calcular desconto corretamente`(preco: Double, percentual: Int, esperado: Double) {
    assertEquals(esperado, service.calcularDesconto(preco, percentual), 0.01)
}
```

### 4. Limpe os mocks entre testes

```kotlin
@AfterEach
fun tearDown() {
    clearAllMocks()
}
```

### 5. Prefira injeção de dependência

Classes que recebem dependências via construtor são infinitamente mais fáceis de testar. Se você precisa de `mockk` em tudo, é sinal de boa arquitetura. Se precisa de reflection ou hacks, algo pode ser melhorado no design.

## Testes vs. design patterns

Se você está usando [design patterns em Kotlin](/blog/kotlin-design-patterns/), como Strategy ou Observer, os testes ficam naturalmente mais simples. Padrões que favorecem composição sobre herança se alinham perfeitamente com a filosofia de MockK.

Para projetos que usam [programação funcional com Arrow](/blog/kotlin-arrow-programacao-funcional/), os testes também mudam de abordagem — com `Either` e `Raise`, você testa caminhos de sucesso e erro sem exceções, o que torna os testes mais previsíveis.

## Conclusão

A combinação de JUnit 5 + MockK é, sem dúvida, o padrão ouro para testes em Kotlin em 2026. MockK entende a linguagem nativamente, JUnit 5 oferece uma estrutura moderna e extensível, e juntos eles permitem escrever testes que são tão idiomáticos quanto o código de produção.

Se você ainda está usando Mockito com Kotlin, considere experimentar MockK — a curva de aprendizado é baixa e os benefícios são imediatos. Comece pelos testes mais simples, explore `coEvery` para código assíncrono e use `@Nested` para organizar seus testes de forma hierárquica.

Se você trabalha com múltiplas linguagens, vale comparar abordagens: em <a href="https://python.dev.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'python.dev.br' })">Python, pytest oferece fixtures e mocking</a> com uma filosofia mais dinâmica, enquanto em <a href="https://golang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">Go, o pacote testing padrão</a> favorece testes sem mocks com interfaces implícitas. Já em <a href="https://rustlang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'rustlang.com.br' })">Rust, testes são integrados ao compilador com cargo test</a>, garantindo segurança de memória até nos testes.

Bons testes não são um custo — são um investimento na qualidade e confiança do seu projeto.
