---
title: "Testando Flow e StateFlow com Turbine em Kotlin | Kotlin Brasil"
url: "https://kotlin.dev.br/blog/testando-flow-stateflow-turbine-kotlin-2026/"
markdown_url: "https://kotlin.dev.br/blog/testando-flow-stateflow-turbine-kotlin-2026.MD"
description: "Aprenda a testar Flow, StateFlow e SharedFlow em Kotlin com Turbine, runTest, ViewModel, eventos de UI e boas práticas para Android e backend."
date: "2026-06-04"
author: "Karina Melo"
---

# Testando Flow e StateFlow com Turbine em Kotlin | Kotlin Brasil

Aprenda a testar Flow, StateFlow e SharedFlow em Kotlin com Turbine, runTest, ViewModel, eventos de UI e boas práticas para Android e backend.


`Flow` virou uma das peças centrais do Kotlin moderno. Ele aparece em ViewModels Android, repositórios com Room, integrações com APIs, pipelines backend, streamings em Ktor e até em fluxos de IA que precisam emitir estado parcial. O problema é que muita equipe aprende a escrever `Flow`, mas continua testando código assíncrono com `delay`, `Thread.sleep`, mocks frágeis ou asserts feitos cedo demais. O resultado são testes lentos, instáveis e pouco confiáveis.

É aí que entra o **Turbine**, uma biblioteca simples e muito prática para testar emissões de `Flow`, `StateFlow` e `SharedFlow`. Em vez de coletar manualmente uma lista, controlar coroutine na mão e torcer para o tempo bater, você escreve o teste como uma conversa com o fluxo: espera o próximo item, valida o valor, confirma que não há eventos extras ou cancela a coleta de forma explícita.

Este guia mostra como usar Turbine com `kotlinx-coroutines-test`, `runTest`, ViewModel e eventos de UI. Se você ainda está revisando a base, leia também nosso conteúdo sobre [Kotlin Flow](/blog/kotlin-flow/), [coroutines em Kotlin](/blog/coroutines-kotlin/) e o [guia de testes Kotlin](/guias/guia-testes-kotlin/). A ideia aqui é sair do conceito e chegar em testes que um time consegue manter em produção.

## Por que testar Flow exige cuidado?

Um `Flow` não é apenas uma função que retorna um valor. Ele pode emitir vários valores, suspender, trocar de dispatcher, combinar fontes, lidar com erro, manter estado quente ou depender do ciclo de vida de quem coleta. Isso muda a forma de testar.

Alguns problemas comuns aparecem rápido:

- o teste termina antes do `Flow` emitir;
- um `delay` real deixa a suíte lenta;
- um `StateFlow` emite o estado inicial e o teste ignora isso;
- um `SharedFlow` perde evento porque ninguém estava coletando;
- uma coroutine fica ativa depois do teste;
- a ordem das emissões muda por causa de concorrência mal controlada.

Turbine ajuda porque coloca essas expectativas no código do teste. Você deixa claro quando espera um item, quando não espera mais nada e quando a coleta deve acabar. Isso reduz testes intermitentes, principalmente em projetos Android com `ViewModel`, `StateFlow` e eventos de tela.

## Dependências recomendadas

Em um projeto Kotlin com Gradle, normalmente você combina Turbine com `kotlinx-coroutines-test`. As versões mudam, então confira a versão atual antes de publicar em um projeto real.

```kotlin
dependencies {
    testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2")
    testImplementation("app.cash.turbine:turbine:1.2.0")
}
```

Para testes Android locais de ViewModel, você também costuma usar JUnit, MockK ou fakes manuais. O importante é que a regra de negócio testada rode na JVM sempre que possível. Testes instrumentados devem ficar para o que depende de framework Android, renderização ou integração real de dispositivo. Essa separação conversa bem com o nosso guia de [testes Android com Compose e Maestro](/guias/testes-android-compose-maestro/).

## Primeiro teste com Flow simples

Vamos começar com um caso pequeno. Imagine um repositório que emite o progresso de sincronização de dados.

```kotlin
class SincronizacaoRepository {
    fun progresso(): Flow<Int> = flow {
        emit(0)
        emit(50)
        emit(100)
    }
}
```

Com Turbine, o teste fica direto:

```kotlin
import app.cash.turbine.test
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals

class SincronizacaoRepositoryTest {
    @Test
    fun `deve emitir progresso em ordem`() = runTest {
        val repository = SincronizacaoRepository()

        repository.progresso().test {
            assertEquals(0, awaitItem())
            assertEquals(50, awaitItem())
            assertEquals(100, awaitItem())
            awaitComplete()
        }
    }
}
```

O ganho é legibilidade. O teste diz exatamente o que o fluxo deve emitir e em qual ordem. `awaitComplete()` confirma que o `Flow` terminou. Se uma emissão faltar, sobrar ou vier fora de ordem, o teste falha de forma compreensível.

## Testando StateFlow em ViewModel

`StateFlow` é um pouco diferente porque sempre possui um valor atual. Quando você começa a coletar, o primeiro item normalmente é o estado inicial. Isso é correto, mas costuma pegar iniciantes de surpresa.

```kotlin
data class BuscaUiState(
    val carregando: Boolean = false,
    val resultados: List<String> = emptyList(),
    val erro: String? = null,
)

class BuscaViewModel(
    private val service: BuscaService,
) : ViewModel() {
    private val _uiState = MutableStateFlow(BuscaUiState())
    val uiState: StateFlow<BuscaUiState> = _uiState.asStateFlow()

    fun buscar(termo: String) {
        viewModelScope.launch {
            _uiState.value = BuscaUiState(carregando = true)
            val resultados = service.buscar(termo)
            _uiState.value = BuscaUiState(resultados = resultados)
        }
    }
}
```

Um teste saudável valida o estado inicial, a transição de carregamento e o estado final:

```kotlin
@Test
fun `deve atualizar estado ao buscar resultados`() = runTest {
    val service = FakeBuscaService(resultados = listOf("Kotlin", "Compose"))
    val viewModel = BuscaViewModel(service)

    viewModel.uiState.test {
        assertEquals(BuscaUiState(), awaitItem())

        viewModel.buscar("kot")

        assertEquals(BuscaUiState(carregando = true), awaitItem())
        assertEquals(
            BuscaUiState(resultados = listOf("Kotlin", "Compose")),
            awaitItem(),
        )

        cancelAndIgnoreRemainingEvents()
    }
}
```

`cancelAndIgnoreRemainingEvents()` é útil quando o fluxo é quente e não termina sozinho, como um `StateFlow` exposto por ViewModel. Sem isso, o teste pode ficar esperando conclusão que nunca virá. O ponto é assumir explicitamente que aquele fluxo continua vivo, mas que as emissões relevantes já foram validadas.

## Testando erro sem depender de exceção solta

Em UI Android, muitas equipes preferem representar erro dentro do estado em vez de deixar exceção escapar para a camada visual. Isso torna o comportamento mais previsível.

```kotlin
@Test
fun `deve mostrar erro quando busca falhar`() = runTest {
    val service = FakeBuscaService(erro = IllegalStateException("API fora do ar"))
    val viewModel = BuscaViewModel(service)

    viewModel.uiState.test {
        awaitItem() // estado inicial

        viewModel.buscar("kotlin")


        assertEquals(true, awaitItem().carregando)

        val estadoErro = awaitItem()
        assertEquals(false, estadoErro.carregando)
        assertEquals("API fora do ar", estadoErro.erro)

        cancelAndIgnoreRemainingEvents()
    }
}
```

Essa abordagem força o ViewModel a transformar falhas em estado visível. Para produto, isso é melhor do que ter uma tela silenciosa ou uma coroutine cancelada sem feedback. Para o teste, também evita depender de stack trace como contrato de comportamento.

## SharedFlow para eventos pontuais

`SharedFlow` costuma aparecer em eventos que não são exatamente estado: navegação, snackbar, analytics, pedido de foco ou confirmação de ação. O cuidado é que eventos podem ser perdidos se a coleta começar tarde demais.

```kotlin
sealed interface PerfilEvento {
    data object NavegarParaHome : PerfilEvento
    data class MostrarMensagem(val texto: String) : PerfilEvento
}

class PerfilViewModel : ViewModel() {
    private val _eventos = MutableSharedFlow<PerfilEvento>()
    val eventos: SharedFlow<PerfilEvento> = _eventos.asSharedFlow()

    fun salvar() {
        viewModelScope.launch {
            _eventos.emit(PerfilEvento.MostrarMensagem("Perfil salvo"))
            _eventos.emit(PerfilEvento.NavegarParaHome)
        }
    }
}
```

No teste, comece a coletar antes de disparar a ação:

```kotlin
@Test
fun `deve emitir eventos ao salvar perfil`() = runTest {
    val viewModel = PerfilViewModel()

    viewModel.eventos.test {
        viewModel.salvar()

        assertEquals(
            PerfilEvento.MostrarMensagem("Perfil salvo"),
            awaitItem(),
        )
        assertEquals(PerfilEvento.NavegarParaHome, awaitItem())

        expectNoEvents()
        cancelAndIgnoreRemainingEvents()
    }
}
```

`expectNoEvents()` documenta que a ação não deve emitir mais nada naquele momento. Esse detalhe parece pequeno, mas captura regressões comuns, como duas snackbars duplicadas ou navegação disparada duas vezes depois de uma refatoração.

## Evite delay real no teste

O erro mais comum em testes de coroutines é colocar `delay(1000)` para esperar emissão. Isso deixa a suíte lenta e ainda não garante determinismo. Com `runTest`, você pode controlar tempo virtual quando o código usa delays bem estruturados.

```kotlin
fun ticker(): Flow<Int> = flow {
    emit(1)
    delay(1_000)
    emit(2)
}

@Test
fun `deve emitir depois do tempo virtual`() = runTest {
    ticker().test {
        assertEquals(1, awaitItem())
        testScheduler.advanceTimeBy(1_000)
        assertEquals(2, awaitItem())
        awaitComplete()
    }
}
```

Tempo virtual torna o teste rápido e previsível. Se o seu código depende de `Dispatchers.IO`, `Dispatchers.Main` ou escopos externos, injete dispatchers no componente testado. Essa prática melhora testabilidade e também deixa a arquitetura mais clara.

## Boas práticas para Turbine em projetos reais

Algumas regras ajudam a manter a suíte saudável:

- valide o estado inicial de `StateFlow` em vez de ignorá-lo sem pensar;
- use fakes simples para regra de negócio antes de recorrer a mocks complexos;
- evite `Thread.sleep` e `delay` real em testes;
- cancele fluxos quentes com `cancelAndIgnoreRemainingEvents()`;
- use `expectNoEvents()` quando ausência de emissão é parte do contrato;
- prefira testar ViewModel e use cases na JVM;
- mantenha testes instrumentados para UI, permissões, navegação real e integração de framework.

Também vale separar teste de transformação e teste de UI. Se uma função transforma `Flow<Pedido>` em `Flow<ResumoPedido>`, teste essa transformação isoladamente. Se o ViewModel apenas conecta a transformação ao estado da tela, teste a transição de `UiState`. Não tente cobrir tudo em um único teste gigante.

## Turbine no backend Kotlin

Embora Turbine apareça muito em Android, ele também é útil no backend. Em Ktor, Spring WebFlux ou pipelines internos, `Flow` pode representar streaming de eventos, processamento de mensagens, tarefas em lote ou respostas parciais. Testar emissões com Turbine ajuda a validar ordem, erro e conclusão sem subir infraestrutura completa.

```kotlin
class RelatorioService {
    fun gerarEventos(): Flow<String> = flow {
        emit("iniciado")
        emit("processando")
        emit("concluido")
    }
}
```

```kotlin
@Test
fun `deve emitir eventos do relatorio`() = runTest {
    val service = RelatorioService()

    service.gerarEventos().test {
        assertEquals("iniciado", awaitItem())
        assertEquals("processando", awaitItem())
        assertEquals("concluido", awaitItem())
        awaitComplete()
    }
}
```

Em times poliglotas, essa disciplina se compara bem a padrões de outras linguagens. O ecossistema <a href="https://python.dev.br/" target="_blank" rel="noopener noreferrer" onclick="umami.track('portfolio-site-click', { destination: 'python.dev.br' })">Python Brasil</a> costuma usar `pytest` para fixtures e testes assíncronos, enquanto Go prioriza testes explícitos com tabelas e concorrência controlada. Em Kotlin, Turbine ocupa um espaço parecido para streams: deixar o comportamento assíncrono verificável sem esconder o contrato.

## Checklist final

Antes de considerar seus testes de `Flow` maduros, revise:

- o teste usa `runTest`;
- emissões são validadas em ordem;
- `StateFlow` tem estado inicial coberto;
- fluxos quentes são cancelados explicitamente;
- não existe `Thread.sleep`;
- dispatchers são injetáveis quando necessário;
- erros são representados de forma testável;
- eventos pontuais não são perdidos por coleta tardia.

Turbine não substitui uma boa arquitetura, mas expõe rapidamente onde ela está frágil. Se um `Flow` é difícil de testar, talvez o problema esteja no excesso de responsabilidade do ViewModel, no dispatcher fixo, no evento modelado como estado ou no estado modelado como evento. Use esses testes como feedback de design, não apenas como uma etapa burocrática do CI.

Para continuar evoluindo, combine este guia com [MVVM em Kotlin](/tutoriais/kotlin-mvvm-tutorial/), [Kotlin Flow](/blog/kotlin-flow/), [testes com JUnit 5 e MockK](/blog/kotlin-testes-junit5-mockk-guia/) e [testes Android com Compose e Maestro](/guias/testes-android-compose-maestro/). A diferença entre um app Kotlin que “funciona na máquina” e um app confiável em produção quase sempre passa por testes assíncronos bem escritos.
