---
title: "Testes em Kotlin: Guia Completo para JVM, Android e Backend | Kotlin Brasil"
url: "https://kotlin.dev.br/guias/guia-testes-kotlin/"
markdown_url: "https://kotlin.dev.br/guias/guia-testes-kotlin.MD"
description: "Aprenda a montar uma estratégia de testes em Kotlin com JUnit 5, MockK, Kotest, coroutines, Flow, Testcontainers, Ktor, Spring Boot e Android."
date: "2025-07-15"
author: "Karina Melo"
---

# Testes em Kotlin: Guia Completo para JVM, Android e Backend | Kotlin Brasil

Aprenda a montar uma estratégia de testes em Kotlin com JUnit 5, MockK, Kotest, coroutines, Flow, Testcontainers, Ktor, Spring Boot e Android.


Testes em Kotlin não são apenas uma etapa burocrática antes do deploy. Eles são a forma mais barata de descobrir se uma regra de negócio continua correta depois de uma refatoração, se uma coroutine respeita cancelamento, se um `Flow` emite os estados esperados, se um endpoint Ktor ou Spring Boot conversa com o banco de verdade e se uma tela Android continua funcionando depois de trocar XML por Jetpack Compose.

Este guia organiza uma estratégia prática para projetos Kotlin em 2026: **JUnit 5** para execução, **MockK** para mocks idiomáticos, **Kotest** quando o time quer uma DSL mais expressiva, `kotlinx-coroutines-test` para código assíncrono, **Turbine** para `Flow`, **Testcontainers** para integração realista e testes Android quando a UI ou o framework precisam entrar na conta.

Se você está começando, não tente automatizar tudo de uma vez. Monte uma pirâmide simples: muitos testes unitários rápidos, alguns testes de integração cobrindo banco/API e poucos testes de ponta a ponta para fluxos críticos. Depois conecte essa suíte ao seu [pipeline de CI/CD em Kotlin](/guias/guia-kotlin-ci-cd/) e complemente com [detekt e ktlint](/blog/detekt-ktlint-kotlin-qualidade-2026/) para manter qualidade de código e comportamento sob controle.

## O que testar em um projeto Kotlin?

Antes das ferramentas, defina o alvo. Um projeto saudável costuma separar os testes assim:

- **Testes unitários**: validam funções, serviços, use cases, validators, mappers e regras de domínio sem I/O real.
- **Testes de coroutines e Flow**: validam concorrência, tempo virtual, cancelamento, retries e sequências de estado.
- **Testes de integração**: sobem banco, fila, HTTP client, repository ou endpoint real para pegar erro de configuração.
- **Testes Android locais**: rodam na JVM com Robolectric ou com componentes isolados quando o Android framework aparece.
- **Testes instrumentados**: rodam em emulador/device e validam Room, Compose, navegação, permissões ou integração com o sistema.
- **Testes end-to-end**: cobrem jornadas de produto com ferramentas como Maestro, Espresso, Playwright mobile ou device farms.

A armadilha comum é pular direto para testes de UI porque eles parecem mais próximos do usuário. Eles são úteis, mas lentos e mais frágeis. A maior parte do comportamento deve estar em camadas testáveis sem Android framework: ViewModel, use case, repository, parser, policy, regra de preço, regra de sincronização e serialização.

## Configuração base com Gradle Kotlin DSL

Para um projeto Kotlin JVM ou backend, comece com dependências parecidas com estas:

```kotlin
dependencies {
    testImplementation(kotlin("test"))
    testImplementation("org.junit.jupiter:junit-jupiter:5.10.3")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")

    testImplementation("io.mockk:mockk:1.13.12")
    testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.1")
    testImplementation("app.cash.turbine:turbine:1.1.0")

    testImplementation("io.kotest:kotest-assertions-core:5.9.1")
    testImplementation("org.testcontainers:junit-jupiter:1.20.1")
    testImplementation("org.testcontainers:postgresql:1.20.1")
}

tasks.test {
    useJUnitPlatform()
}
```

Em Android, a ideia é parecida, mas as configurações entram nos blocos `testImplementation` e `androidTestImplementation`:

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

    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
    androidTestImplementation("androidx.compose.ui:ui-test-junit4")
}
```

Use versões compatíveis com o Android Gradle Plugin e com o Kotlin do seu projeto. O objetivo aqui é mostrar o desenho, não congelar números para todos os times.

## Testes unitários com JUnit 5

JUnit 5 continua sendo a opção mais comum para suites Kotlin porque é simples, estável e integra bem com Gradle, IDEs e CI. Kotlin permite nomes de teste com backticks, o que deixa o relatório mais legível:

```kotlin
class CalculadoraDeFreteTest {
    private val calculadora = CalculadoraDeFrete()

    @Test
    fun `deve aplicar frete gratis acima do valor minimo`() {
        val frete = calculadora.calcular(
            subtotal = 250.0,
            cep = "01001-000",
        )

        assertEquals(0.0, frete)
    }

    @Test
    fun `deve rejeitar subtotal negativo`() {
        assertThrows<IllegalArgumentException> {
            calculadora.calcular(subtotal = -10.0, cep = "01001-000")
        }
    }
}
```

Prefira testar comportamento observável. Se uma refatoração interna quebra muitos testes sem mudar resultado público, talvez os testes estejam presos à implementação. Esse é um sinal para testar pelo contrato da classe, não por detalhes como “chamou método privado X”.

## MockK para dependências externas

MockK entende melhor o Kotlin do que mocks genéricos do ecossistema Java: classes finais, funções suspensas, objetos, `relaxed mocks` e DSL idiomática. Use mocks para isolar I/O, APIs, repositórios externos e relógios, mas evite mockar tudo.

```kotlin
class PedidoServiceTest {
    private val gateway = mockk<PagamentoGateway>()
    private val repository = mockk<PedidoRepository>()
    private val service = PedidoService(gateway, repository)

    @Test
    fun `deve salvar pedido aprovado`() {
        every { gateway.autorizar(any()) } returns Autorizacao(aprovada = true)
        every { repository.salvar(any()) } returns Pedido(id = 42, status = "APROVADO")

        val pedido = service.criar(PedidoRequest(valor = 120.0))

        assertEquals("APROVADO", pedido.status)
        verify(exactly = 1) { repository.salvar(match { it.valor == 120.0 }) }
    }
}
```

Quando o teste exige cinco ou seis mocks para uma classe pequena, investigue o design. Pode ser que a classe esteja coordenando responsabilidades demais ou que falte uma abstração mais simples.

## Coroutines com `runTest`

Para funções suspensas, use `runTest`. Ele controla tempo virtual, evita sleeps reais e ajuda a detectar coroutines penduradas:

```kotlin
class SincronizadorTest {
    private val api = mockk<TarefasApi>()
    private val dao = mockk<TarefaDao>(relaxed = true)
    private val sincronizador = Sincronizador(api, dao)

    @Test
    fun `deve marcar tarefa como sincronizada depois do envio`() = runTest {
        coEvery { api.enviar(any()) } returns Unit

        sincronizador.enviar(TarefaPendente(id = 10, titulo = "Revisar PR"))

        coVerify { api.enviar(match { it.id == 10L }) }
        coVerify { dao.marcarComoSincronizada(10L) }
    }
}
```

Evite `runBlocking` em testes novos. Ele pode mascarar problemas de tempo, deixar a suíte mais lenta e dificultar cenários com delay, timeout e retry.

## Testando Flow com Turbine

`Flow` aparece em Android, backend reativo e pipelines assíncronos. Para validar emissões, Turbine deixa o teste explícito:

```kotlin
@Test
fun `deve emitir loading e sucesso ao carregar perfil`() = runTest {
    coEvery { api.buscarPerfil() } returns Perfil(nome = "Ana")

    viewModel.estado.test {
        assertEquals(PerfilState.Loading, awaitItem())

        viewModel.carregar()

        assertEquals(PerfilState.Sucesso("Ana"), awaitItem())

        cancelAndIgnoreRemainingEvents()
    }
}
```

Em ViewModels Android, configure o dispatcher principal no teste com uma regra própria ou extensão. O ponto é não depender do `Dispatchers.Main` real em ambiente JVM.

## Integração com Testcontainers

Mocks são ótimos para regras, mas ruins para provar que SQL, migrations, JSONB, índices e transações funcionam. Para backend Kotlin com Ktor, Spring Boot ou Exposed, Testcontainers costuma pagar o custo rapidamente:

```kotlin
@Testcontainers
class UsuarioRepositoryTest {
    companion object {
        @Container
        val postgres = PostgreSQLContainer("postgres:16-alpine")
    }

    @Test
    fun `deve persistir usuario com email unico`() {
        val dataSource = criarDataSource(postgres.jdbcUrl, postgres.username, postgres.password)
        val repository = UsuarioRepository(dataSource)

        val usuario = repository.criar(email = "ana@example.com")

        assertNotNull(usuario.id)
        assertEquals("ana@example.com", repository.buscarPorId(usuario.id)?.email)
    }
}
```

Esse tipo de teste é mais lento que um unitário, então rode menos casos e escolha cenários de alto risco: migrations, constraints, queries complexas, paginação, transações e serialização. Para uma API Ktor completa, combine com testes de rota e um banco real. Para Spring Boot, use slices como `@DataJpaTest` quando fizer sentido.

## Testes Android: local, Compose e instrumentado

Em Android, tente empurrar regra de negócio para fora da Activity/Composable. ViewModel com `StateFlow`, use cases e repositories são fáceis de testar na JVM. Deixe testes instrumentados para o que realmente depende do framework. Para uma trilha dedicada ao ecossistema mobile, veja o guia de [testes Android com Kotlin, Compose e Maestro](/guias/testes-android-compose-maestro/).

Para Compose, use tags estáveis:

```kotlin
@Test
fun loginValido_deveAbrirHome() {
    composeRule.setContent { LoginScreen(onLogin = { sucesso = true }) }

    composeRule.onNodeWithTag("email_field").performTextInput("ana@example.com")
    composeRule.onNodeWithTag("password_field").performTextInput("senha-segura")
    composeRule.onNodeWithTag("login_button").performClick()

    composeRule.onNodeWithTag("home_screen").assertIsDisplayed()
}
```

Para jornadas maiores, Maestro pode ser mais simples que manter centenas de interações Espresso. Ainda assim, não transforme toda regra em teste E2E. Um fluxo de login, uma compra, um sync offline-first e um deep link crítico podem bastar.

## Onde encaixar no CI/CD

Uma pipeline realista para Kotlin deve separar custo e feedback:

1. `./gradlew test` em todo pull request.
2. `./gradlew ktlintCheck detekt` junto com testes unitários.
3. Testes de integração com Testcontainers em PRs que mexem em backend, repository ou banco.
4. Testes instrumentados Android em branch principal, PRs críticos ou matriz reduzida de emuladores.
5. Testes E2E apenas para fluxos de negócio que justificam manutenção.

O [guia de GitHub Actions para Kotlin](/blog/kotlin-github-actions/) mostra a base do workflow. O importante é não esconder falhas com `continue-on-error` em etapas essenciais. Teste instável deve ser corrigido, isolado ou removido; não deve virar ruído permanente.

## Checklist de uma boa suíte Kotlin

- Testes unitários rodam em segundos e não dependem de rede, banco ou relógio real.
- Nomes de teste explicam cenário, ação e expectativa.
- Coroutines usam `runTest`, dispatchers controlados e tempo virtual quando necessário.
- `Flow` é validado por emissões, não por sleeps.
- Integração usa banco real quando SQL/migration importa.
- Android mantém regra fora da UI e testa Compose apenas onde a tela é o contrato.
- Fixtures e builders reduzem duplicação sem esconder o caso testado.
- CI bloqueia regressões importantes e mantém logs legíveis.

## Erros comuns

- **Mockar o domínio inteiro**: se tudo é mock, o teste só prova que o mock foi configurado.
- **Testar detalhe interno**: refatorações simples quebram a suíte sem mudar comportamento.
- **Usar delay real**: deixa testes lentos e instáveis; prefira tempo virtual.
- **Ignorar cenários de erro**: timeout, resposta vazia, conflito, permissão negada e dado inválido costumam quebrar produção.
- **Confiar apenas em SQLite em memória**: para PostgreSQL, MySQL ou SQL Server, comportamento de tipos e constraints pode mudar.
- **Rodar E2E demais**: teste lento demais vira gargalo e incentiva o time a ignorar a suíte.

## Próximos passos

Se seu projeto hoje quase não tem testes, comece por uma regra de domínio que muda com frequência e por um bug recente que não deveria voltar. Depois cubra ViewModels, repositories críticos e um fluxo de integração com banco. Para Android offline-first, complemente este guia com a arquitetura de [Room, WorkManager e Flow](/blog/android-offline-first-kotlin-2026/). Para backend, avance para [Ktor](/guias/guia-kotlin-backend-ktor/), [Spring Boot](/blog/kotlin-spring-boot/) e [PostgreSQL com Kotlin](/tutoriais/kotlin-postgresql-backend/).

Uma suíte bem desenhada não garante que o produto esteja certo, mas reduz muito o custo de evoluir com confiança. Em Kotlin, a combinação de linguagem expressiva, coroutines testáveis, DSLs claras e integração forte com Gradle permite chegar nesse ponto sem transformar testes em um projeto paralelo.

Para comparar estilos em outros ecossistemas, veja como <a href="https://golang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">Go testa com o pacote testing integrado</a> e como <a href="https://python.dev.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'python.dev.br' })">Python usa pytest para testes expressivos</a>.
