---
title: "Testes Unitários em Kotlin Tutorial em Português — Passo a Passo | Kotlin Brasil"
url: "https://kotlin.dev.br/tutoriais/kotlin-testes-unitarios/"
markdown_url: "https://kotlin.dev.br/tutoriais/kotlin-testes-unitarios.MD"
description: "Aprenda testes unitários em Kotlin com JUnit 5 e MockK. Tutorial completo com exemplos de assertions, mocking, coroutines e boas práticas."
date: "2025-07-20"
author: "Karina Melo"
---

# Testes Unitários em Kotlin Tutorial em Português — Passo a Passo | Kotlin Brasil

Aprenda testes unitários em Kotlin com JUnit 5 e MockK. Tutorial completo com exemplos de assertions, mocking, coroutines e boas práticas.


Testes unitários são a base de qualquer projeto profissional em Kotlin. Neste tutorial, vamos aprender a escrever testes eficazes usando **JUnit 5** e **MockK**, desde a configuração inicial até técnicas avançadas como testes parametrizados e testes de [coroutines](/glossario/coroutine/). Se você quer entregar código com confiança, este guia é para você.

## Por que Testar?

Testes unitários verificam se unidades individuais do seu código (funções, [classes](/glossario/class/), métodos) funcionam corretamente de forma isolada. Eles oferecem feedback rápido durante o desenvolvimento, documentam o comportamento esperado do sistema e protegem contra regressões quando você refatora ou adiciona novas features. Em Kotlin, a expressividade da linguagem torna os testes especialmente legíveis e concisos.

## Passo 1: Configuração do Projeto com JUnit 5

Primeiro, configure as dependências no `build.gradle.kts`:

```kotlin
// build.gradle.kts
plugins {
    kotlin("jvm") version "2.0.0"
}

dependencies {
    testImplementation(kotlin("test"))
    testImplementation("org.junit.jupiter:junit-jupiter:5.10.3")
    testImplementation("io.mockk:mockk:1.13.12")
    testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.1")
}

tasks.test {
    useJUnitPlatform()
}
```

Com `kotlin("test")`, o Kotlin já inclui as assertions básicas. O JUnit 5 serve como engine de execução, e o MockK é a biblioteca de mocking idiomática para Kotlin.

## Passo 2: Escrevendo seu Primeiro Teste

Vamos começar com uma [classe](/glossario/class/) simples e seus testes correspondentes:

```kotlin
// src/main/kotlin/Calculadora.kt
class Calculadora {
    fun somar(a: Int, b: Int): Int = a + b
    fun dividir(a: Double, b: Double): Double {
        require(b != 0.0) { "Divisor nao pode ser zero" }
        return a / b
    }
    fun ehPar(numero: Int): Boolean = numero % 2 == 0
}
```

Agora, o teste:

```kotlin
// src/test/kotlin/CalculadoraTest.kt
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import kotlin.test.assertEquals
import kotlin.test.assertTrue
import kotlin.test.assertFalse

class CalculadoraTest {
    private val calc = Calculadora()

    @Test
    fun `deve somar dois numeros corretamente`() {
        assertEquals(5, calc.somar(2, 3))
        assertEquals(0, calc.somar(-1, 1))
        assertEquals(-4, calc.somar(-2, -2))
    }

    @Test
    fun `deve dividir dois numeros`() {
        assertEquals(2.5, calc.dividir(5.0, 2.0))
    }

    @Test
    fun `deve lancar excecao ao dividir por zero`() {
        assertThrows<IllegalArgumentException> {
            calc.dividir(10.0, 0.0)
        }
    }

    @Test
    fun `deve verificar se numero eh par`() {
        assertTrue(calc.ehPar(4))
        assertFalse(calc.ehPar(7))
    }
}
```

Note que Kotlin permite nomes de testes com espaços usando backticks, o que melhora enormemente a legibilidade dos relatórios de testes. Adote essa prática para descrever o comportamento esperado de forma clara.

## Passo 3: Ciclo de Vida dos Testes

O JUnit 5 oferece annotations para controlar o ciclo de vida dos testes. Use `@BeforeEach` para configurar estado antes de cada teste e `@AfterEach` para limpeza:

```kotlin
import org.junit.jupiter.api.*

class RepositorioUsuariosTest {
    private lateinit var repositorio: RepositorioUsuarios

    @BeforeEach
    fun configurar() {
        repositorio = RepositorioUsuarios()
        repositorio.adicionar(Usuario(1, "Ana", "ana@email.com"))
    }

    @AfterEach
    fun limpar() {
        repositorio.limparTodos()
    }

    @Test
    fun `deve encontrar usuario por id`() {
        val usuario = repositorio.buscarPorId(1)
        assertEquals("Ana", usuario?.nome)
    }

    @Test
    fun `deve retornar null para id inexistente`() {
        val usuario = repositorio.buscarPorId(999)
        assertNull(usuario)
    }

    companion object {
        @JvmStatic
        @BeforeAll
        fun inicializacao() {
            println("Executado uma vez antes de todos os testes")
        }
    }
}
```

O `@BeforeAll` é útil para inicializações pesadas como conexão com banco de dados de teste. Note o uso de [companion object](/glossario/companion-object/) com `@JvmStatic`, exigido pelo JUnit 5 para métodos estáticos.

## Passo 4: Mocking com MockK

O **MockK** é a biblioteca de mocking mais popular para Kotlin. Diferente do Mockito, ele foi criado especificamente para Kotlin e suporta nativamente [coroutines](/glossario/coroutine/), [extension functions](/glossario/extension-function/) e [data classes](/glossario/data-class/).

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

interface ServicoEmail {
    fun enviar(destinatario: String, assunto: String, corpo: String): Boolean
}

class ServicoNotificacao(private val email: ServicoEmail) {
    fun notificarUsuario(usuario: Usuario, mensagem: String): Boolean {
        return email.enviar(usuario.email, "Notificação", mensagem)
    }
}

class ServicoNotificacaoTest {
    private val emailMock = mockk<ServicoEmail>()
    private val serviço = ServicoNotificacao(emailMock)

    @Test
    fun `deve enviar email para o usuario`() {
        every { emailMock.enviar(any(), any(), any()) } returns true

        val resultado = serviço.notificarUsuario(
            Usuario(1, "Carlos", "carlos@email.com"),
            "Sua conta foi ativada"
        )

        assertTrue(resultado)
        verify {
            emailMock.enviar("carlos@email.com", "Notificação", "Sua conta foi ativada")
        }
    }

    @Test
    fun `deve retornar false quando envio falhar`() {
        every { emailMock.enviar(any(), any(), any()) } returns false

        val resultado = serviço.notificarUsuario(
            Usuario(1, "Ana", "ana@email.com"),
            "Teste"
        )

        assertFalse(resultado)
    }
}
```

O `every { ... } returns ...` define o comportamento do mock, e `verify { ... }` confirma que o método foi chamado com os argumentos corretos. O `any()` é um matcher que aceita qualquer valor.

## Passo 5: Testando Coroutines com runTest

Para testar funções [suspend](/glossario/suspend/), use o `runTest` do módulo `kotlinx-coroutines-test`. Ele controla o tempo virtual, permitindo testar delays sem esperar o tempo real.

```kotlin
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.delay
import io.mockk.coEvery
import io.mockk.coVerify

interface RepositorioRemoto {
    suspend fun buscarDados(id: String): String
}

class CasoDeUso(private val repositorio: RepositorioRemoto) {
    suspend fun executar(id: String): Result<String> {
        return try {
            val dados = repositorio.buscarDados(id)
            Result.success(dados)
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
}

class CasoDeUsoTest {
    private val repoMock = mockk<RepositorioRemoto>()
    private val casoDeUso = CasoDeUso(repoMock)

    @Test
    fun `deve retornar sucesso quando repositorio responde`() = runTest {
        coEvery { repoMock.buscarDados("123") } returns "dados-do-servidor"

        val resultado = casoDeUso.executar("123")

        assertTrue(resultado.isSuccess)
        assertEquals("dados-do-servidor", resultado.getOrNull())
        coVerify { repoMock.buscarDados("123") }
    }

    @Test
    fun `deve retornar falha quando repositorio lanca excecao`() = runTest {
        coEvery { repoMock.buscarDados(any()) } throws RuntimeException("Erro de rede")

        val resultado = casoDeUso.executar("456")

        assertTrue(resultado.isFailure)
    }
}
```

Note o uso de `coEvery` e `coVerify` (prefixo `co`) para funções suspend. O `runTest` avança automaticamente o tempo virtual quando encontra chamadas a `delay`, então seus testes executam instantaneamente.

## Passo 6: Testes Parametrizados

Testes parametrizados permitem executar o mesmo teste com diferentes entradas, reduzindo duplicação:

```kotlin
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.CsvSource
import org.junit.jupiter.params.provider.MethodSource
import java.util.stream.Stream

class ValidadorTest {
    @ParameterizedTest
    @CsvSource(
        "teste@email.com, true",
        "invalido, false",
        "user@domain.org, true",
        "'', false",
        "sem-arroba.com, false"
    )
    fun `deve validar formato de email`(email: String, esperado: Boolean) {
        val validador = ValidadorEmail()
        assertEquals(esperado, validador.ehValido(email))
    }

    @ParameterizedTest
    @MethodSource("fornecerSenhas")
    fun `deve validar forca da senha`(caso: CasoSenha) {
        val validador = ValidadorSenha()
        assertEquals(caso.esperado, validador.ehForte(caso.senha))
    }

    companion object {
        @JvmStatic
        fun fornecerSenhas(): Stream<CasoSenha> = Stream.of(
            CasoSenha("123", false),
            CasoSenha("Abc@1234", true),
            CasoSenha("senhafraca", false),
            CasoSenha("F0rte!Senh@", true)
        )
    }

    data class CasoSenha(val senha: String, val esperado: Boolean)
}
```

O `@CsvSource` é prático para dados simples. Para casos mais complexos, use `@MethodSource` com um método que retorna um `Stream` de objetos.

## Boas Práticas para Código Testável

Escrever testes eficientes começa com código bem estruturado. Aplique **injeção de dependências** — receba dependências via construtor ao invés de criá-las internamente. Use [interfaces](/glossario/interface/) para definir contratos que podem ser facilmente mockados. Mantenha funções pequenas e com responsabilidade única. Evite estado global e efeitos colaterais ocultos.

Siga o padrão **AAA** (Arrange, Act, Assert): configure o cenário, execute a ação e verifique o resultado. Cada teste deve ser independente e não depender da ordem de execução.

## Erros Comuns

**1. Testes que dependem uns dos outros:** Cada teste deve configurar seu próprio estado. Nunca assuma que um teste rodou antes de outro. Use `@BeforeEach` para garantir estado limpo.

**2. Testar implementação ao invés de comportamento:** Evite verificar detalhes internos como quantas vezes um método privado foi chamado. Teste o resultado observável — a saída da [função](/glossario/fun/), o estado final do [objeto](/glossario/object/).

**3. Mocks excessivos:** Se você precisa de muitos mocks em um teste, provavelmente o código tem acoplamento alto. Refatore para reduzir dependências antes de adicionar mais mocks.

**4. Não testar cenários de erro:** Teste não apenas o caminho feliz. Verifique exceções, [valores nulos](/glossario/nullable/), listas vazias e entradas inválidas.

**5. Esquecer `coEvery` para suspend functions:** Usar `every` ao invés de `coEvery` com funções suspend causa erros confusos em tempo de execução. Sempre use a variante `co` para coroutines.

## Conclusão e Próximos Passos

Neste tutorial, cobrimos o ecossistema completo de testes unitários em Kotlin: JUnit 5 para estrutura e asserções, MockK para mocking idiomático, `runTest` para coroutines e testes parametrizados para reduzir duplicação. Com essas ferramentas, você tem tudo que precisa para criar uma suíte de testes robusta e confiável.

Como próximos passos, explore **testes de integração** com TestContainers para bancos de dados, **testes de API** com o módulo de testes do Ktor, e ferramentas de **cobertura de código** como JaCoCo. Confira também nosso tutorial sobre [Coroutines Avançadas](/tutoriais/coroutines-avançado/) para entender melhor o `runTest` e padrões avançados de concorrência testável. Frameworks de teste modernos existem em todos os ecossistemas — <a href="https://python.dev.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'python.dev.br' })">Python conta com pytest para testes elegantes</a> e <a href="https://golang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">Go possui testing nativo na biblioteca padrão</a>.
