1 – Introdução
Testar código assíncrono é, muitas vezes, um desafio. Funções que utilizam corrotinas podem ter comportamento imprevisível devido a atrasos, execução concorrente e mudanças de contexto. Felizmente, o Kotlin oferece ferramentas nativas e bibliotecas auxiliares para simplificar os testes unitários com corrotinas.
Neste artigo, exploraremos como usar ferramentas como UnconfinedTestDispatcher
, TestCoroutineScheduler
, e a biblioteca Turbine para testar corrotinas e fluxos de forma eficiente.
2 – O Problema de Testar Código Assíncrono
Testar código assíncrono é complicado porque:
- Corrotinas podem ser executadas em diferentes threads ou contextos.
- Métodos como
delay
ouwithTimeout
introduzem esperas reais que podem tornar os testes lentos. - Fluxos (
Flow
) dependem de eventos assíncronos que precisam ser controlados.
Sem as ferramentas corretas, testes podem se tornar inconsistentes (flaky) ou levar mais tempo do que o necessário.
3 – Ferramentas Nativas do Kotlin
3.1 – UnconfinedTestDispatcher
- O que é? Um dispatcher projetado para testes que não está vinculado a threads específicas.
- Por que usar? Permite executar corrotinas sem restrições de contexto, tornando os testes previsíveis.
- Exemplo:
import kotlinx.coroutines.*
import kotlinx.coroutines.test.*
fun main() = runTest {
// O testScheduler é um TestCoroutineScheduler fornecido automaticamente pelo runTest.
// Ele gerencia o tempo virtual para todas as corrotinas neste teste.
val testDispatcher = UnconfinedTestDispatcher(testScheduler)
// Utilizamos o testDispatcher para garantir que todas as operações compartilhem além do mesmo scheduler, também o mesmo dispatcher.
withContext(testDispatcher) {
println("Executando no UnconfinedTestDispatcher: ${Thread.currentThread().name}")
delay(1000) // Simula um atraso de 1 segundo virtual
println("Finalizando no UnconfinedTestDispatcher.")
}
}
3.2 – TestCoroutineScheduler
- O que é? Um agendador que permite avançar o tempo manualmente em testes.
-
Por que usar?
Para controlar tarefas baseadas em tempo (
delay
,withTimeout
) sem esperar o tempo real passar. - Exemplo:
import kotlinx.coroutines.*
import kotlinx.coroutines.test.*
import org.junit.Test
class TestesCoroutines {
// Cria um TestCoroutineScheduler manualmente
private val scheduler = TestCoroutineScheduler()
// Cria um dispatcher baseado no scheduler manual
private var testDispatcher: TestDispatcher = UnconfinedTestDispatcher(scheduler)
@Test
fun testCoroutines() {
// Usa runTest com o dispatcher configurado
runTest(testDispatcher) {
println("Tarefa iniciada.")
// Lança uma corrotina no mesmo dispatcher
delay(1000) // Simula um atraso de 1 segundo virtual
println("Tarefa concluída.")
// Avança o tempo virtualmente em 1 segundo
scheduler.advanceTimeBy(1000)
}
}
}
Por que precisamos do @Test
?
- A anotação
@Test
é usada para marcar um método como um caso de teste, permitindo que ele seja executado pelo JUnit. - Sem o
@Test
, o método testCoroutines seria tratado como um método normal e não apareceria como executável no IDE. - Quando o JUnit detecta o
@Test
, ele:- Instancia automaticamente a classe de teste.
- Executa o método marcado, com todas as dependências configuradas (como
scheduler
etestDispatcher
).
3.3 – runTest
- O que é? Uma função que fornece um ambiente controlado para executar corrotinas em testes.
-
Por que usar?
Simplifica a configuração de testes assíncronos, substituindo
runBlocking
em testes. - Exemplo:
import kotlinx.coroutines.*
import kotlinx.coroutines.test.*
fun main() = runTest {
println("Iniciando o teste.")
delay(1000) // Isso não atrasa o teste real
println("Teste finalizado.")
}
4 - Testando fluxos com a biblioteca turbine
Para fluxos (Flow), a biblioteca Turbine simplifica a coleta e validação de valores emitidos.
4.1 – O que é Turbine?
Turbine é uma biblioteca projetada especificamente para testar fluxos no Kotlin, permitindo validar os itens emitidos e eventos como cancelamento ou conclusão.
- Exemplo com turbine:
import app.cash.turbine.test
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.test.runTest
fun main() = runTest {
val fluxo = flow {
emit(1)
delay(1000)
emit(2)
}
fluxo.test {
val item1 = awaitItem()
println("Recebido: $item1") // Exibe o primeiro valor no terminal
assert(item1 == 1) // Valida o primeiro item
val item2 = awaitItem()
println("Recebido: $item2") // Exibe o segundo valor no terminal
assert(item2 == 2) // Valida o segundo item
awaitComplete() // Confirma que o fluxo foi concluído
println("Fluxo concluido!") // Indica que o fluxo terminou
}
}
5 – Comparação de Ferramentas
Ferramenta | Função | Quando usar |
---|---|---|
UnconfinedTestDispatcher | Executa corrotinas sem restrições de contexto. | Testes simples que não dependem de tempo real. |
TestCoroutineScheduler | Controla tempo manualmente em testes. | Testes com delay , timeout , ou eventos de tempo. |
Turbine | Testa valores emitidos por Flow . |
Testes específicos de fluxos. |
6 – Conclusão
Testar corrotinas e fluxos no Kotlin pode ser desafiador, mas com as ferramentas certas, é possível criar testes rápidos, eficientes e confiáveis. O uso de UnconfinedTestDispatcher
, TestCoroutineScheduler
, e bibliotecas como o Turbine torna o processo muito mais simples.
Resumo:
- Controle de tempo: Use TestCoroutineScheduler para gerenciar atrasos e agendamentos.
- Ambientes de teste: runTest é a base para testes assíncronos.
- Testes de fluxo: Turbine é a solução ideal para validar fluxos.
Agora que você conhece as melhores práticas para testar corrotinas no Kotlin, experimente aplicá-las nos seus projetos!
No próximo artigo, iremos discorrer um pouquinho mais a respeito de exemplos de assincronia, somente para explorarmos o assunto um pouco mais e fixarmos o conteúdo melhor.