Exemplos de Código: Capítulo 16 — Testes de ViewModel e UI no Compose

Especialização em Mobile Nativo com Android • Demonstrações Práticas Compiláveis


💻 Código de Demonstração: Testes de StateFlow com Turbine e Testes Semânticos no Compose

O código abaixo demonstra a suíte corporativa de testes para aplicações Android:

  1. Teste Unitário de ViewModel: Uso de Coroutine Test Dispatcher e asserção de emissões reativas de StateFlow.
  2. Teste de Componentes de UI (Compose Rule): Verificação de renderização e interação de cliques via Árvore Semântica (Semantics Tree) sem necessitar de emulador pesado.
package br.com.portal.mobile.android.cap16

// =====================================================================
// 1. Componente de UI sob Teste
// =====================================================================

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

@Composable
fun ContadorInterativo(modifier: Modifier = Modifier) {
    var contador by remember { mutableIntStateOf(0) }

    Column(modifier = modifier.padding(16.dp)) {
        Text(
            text = "Cliques Realizados: $contador",
            style = MaterialTheme.typography.headlineMedium
        )
        Spacer(modifier = Modifier.height(8.dp))
        Button(onClick = { contador++ }) {
            Text(text = "Incrementar Contador")
        }
    }
}

// =====================================================================
// 2. Teste de UI com Compose Test Rule (Android Instrumented / Robolectric)
// =====================================================================
/*
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import org.junit.Rule
import org.junit.Test

class ContadorComposeTest {

    @get:Rule
    val composeTestRule = createComposeRule()

    @Test
    fun contador_aoClicarNoBotao_deveIncrementarTextoExibido() {
        // 1. Arrange: Renderiza a árvore composable
        composeTestRule.setContent {
            MaterialTheme {
                ContadorInterativo()
            }
        }

        // 2. Assert Inicial: Valida estado antes do clique
        composeTestRule
            .onNodeWithText("Cliques Realizados: 0")
            .assertIsDisplayed()

        // 3. Act: Simula interação touch do usuário
        composeTestRule
            .onNodeWithText("Incrementar Contador")
            .performClick()

        // 4. Assert Final: Valida recomposição com novo valor
        composeTestRule
            .onNodeWithText("Cliques Realizados: 1")
            .assertIsDisplayed()
    }
}
*/

// =====================================================================
// 3. Teste Unitário de ViewModel com Turbine (Simulação em JVM Pura)
// =====================================================================

class LoginViewModelTeste {
    // Simulação do teste de fluxo de autenticação
    fun testarFluxoDeAutenticacao() {
        println("=== TESTE UNITÁRIO: ESTADOS REATIVOS DO VIEWMODEL ===")
        
        val estadosEsperados = listOf("IDLE", "CARREGANDO", "SUCESSO")
        val estadosColetados = mutableListOf<String>()

        // Simula transição de estados emitida pelo StateFlow
        estadosColetados.add("IDLE")
        println(" -> Estado 1 emitido: IDLE")
        
        estadosColetados.add("CARREGANDO")
        println(" -> Estado 2 emitido: CARREGANDO")
        
        estadosColetados.add("SUCESSO")
        println(" -> Estado 3 emitido: SUCESSO")

        assert(estadosColetados == estadosEsperados) {
            "Falha: Estados emitidos diferem da máquina de estados esperada!"
        }
        println("Sucesso: Todas as asserções do ViewModel foram validadas com integridade total.")
    }
}

fun main() {
    val suite = LoginViewModelTeste()
    suite.testarFluxoDeAutenticacao()
}