APIs de teste do Compose v2

As versões 2 das APIs de teste do Compose (createComposeRule, createAndroidComposeRule, runComposeUiTest, runAndroidComposeUiTest etc.) já estão disponíveis para melhorar o controle sobre a execução de corrotinas. Essa atualização não duplica toda a superfície da API. Apenas as APIs que estabelecem o ambiente de teste foram atualizadas.

As APIs v1 foram descontinuadas, e é altamente recomendável migrar para as novas APIs. A migração verifica se os testes estão alinhados com o comportamento padrão de corrotinas e evita problemas de compatibilidade no futuro. Para conferir uma lista das APIs v1 descontinuadas, consulte Mapeamentos de API.

Essas mudanças estão incluídas em androidx.compose.ui:ui-test-junit4:1.11.0-alpha03+ e androidx.compose.ui:ui-test:1.11.0-alpha03+.

Enquanto as APIs v1 dependiam do UnconfinedTestDispatcher, as APIs v2 usam o StandardTestDispatcher por padrão para a composição em execução. Essa mudança alinha o comportamento de teste do Compose com as APIs runTest padrão e oferece controle explícito sobre a ordem de execução da corrotina.

Configurar o ambiente de teste

As APIs de teste do Compose v2 usam ComposeUiTestConfig para personalizar o ambiente de teste. As APIs que criam funções de configuração para testes, como createComposeRule, runComposeUiTest e outras APIs relacionadas, aceitam ComposeUiTestConfig. Esse objeto de configuração consolida APIs relacionadas ao ambiente, como effectContext, runTestContext e testTimeout, em um único objeto.

O modelo de configuração também gerencia inputMode. As APIs de teste do Compose v2 aplicam InputMode.Touch por padrão no início de cada teste para garantir o determinismo e evitar que o estado do modo de entrada vaze entre os testes.

ComposeUiTestConfig faz parte das APIs Compose Test v2, que usam StandardTestDispatcher por padrão. Se os testes usarem APIs v1, consulte Migrar para APIs de teste v2 antes de adotar ComposeUiTestConfig.

Migrar para ComposeUiTestConfig

Nas sobrecargas para criar funções de configuração em testes, várias sobrecargas que aceitam parâmetros de configuração individuais, como effectContext, runTestContext ou testTimeout, foram descontinuadas. Atualize seus testes para usar ComposeUiTestConfig, conforme mostrado no exemplo a seguir:

Modo de entrada padrão

Os testes podem falhar durante a migração se dependerem de modos de entrada sem toque configurados por APIs de instrumentação antes do início do teste. Nas funções de configuração para testes, o sistema aplica InputMode.Touch por padrão no início de cada teste para aumentar o determinismo e evitar vazamento de estado, substituindo o estado do dispositivo ambiente e a configuração pré-teste.

Para resolver isso, especifique o modo de entrada necessário em ComposeUiTestConfig:

class FocusTest {
    @get:Rule
    val rule = createComposeRule(
        config = ComposeUiTestConfig(inputMode = InputMode.Keyboard)
    )

    @Test
    fun testFocus() {}
}

Para configurar o modo de entrada para casos de teste individuais em vez de toda a classe de teste, transmita ComposeUiTestConfig para runComposeUiTest:

class FocusTest {
    @Test
    fun testTouchMode() = runComposeUiTest {
        // Runs with the default InputMode.Touch
    }

    @Test
    fun testKeyboardMode() = runComposeUiTest(
        ComposeUiTestConfig(inputMode = InputMode.Keyboard)
    ) {
        // Runs with InputMode.Keyboard
    }
}

Para outros problemas e soluções de migração, consulte Falhas comuns e como corrigi-las.

Migrar para as APIs de teste da v2

Ao fazer upgrade para as APIs v2, você pode usar Localizar e substituir para atualizar as importações de pacotes e adotar as novas mudanças de dispatcher.

Como alternativa, peça ao Gemini para fazer uma migração para a v2 das APIs de teste do Compose com o seguinte comando:

Migrar das APIs de teste v1 para as APIs de teste v2

Essa solicitação vai usar este guia para migrar para as APIs de teste da v2.

Migrate to Compose testing v2 APIs using the official
migration guide.

Como usar comandos de IA

Os comandos de IA são destinados ao uso no Gemini no Android Studio.

Saiba mais sobre o Gemini no Studio aqui: https://developer.android.com/studio/gemini/overview

Use a tabela a seguir para mapear as APIs v1 descontinuadas e as substituições na v2:

Descontinuado (v1)

Substituição (v2)

androidx.compose.ui.test.junit4.createComposeRule

androidx.compose.ui.test.junit4.v2.createComposeRule

androidx.compose.ui.test.junit4.createAndroidComposeRule

androidx.compose.ui.test.junit4.v2.createAndroidComposeRule

androidx.compose.ui.test.junit4.createEmptyComposeRule

androidx.compose.ui.test.junit4.v2.createEmptyComposeRule

androidx.compose.ui.test.junit4.AndroidComposeTestRule

androidx.compose.ui.test.junit4.v2.AndroidComposeTestRule

androidx.compose.ui.test.runComposeUiTest

androidx.compose.ui.test.v2.runComposeUiTest

androidx.compose.ui.test.runAndroidComposeUiTest

androidx.compose.ui.test.v2.runAndroidComposeUiTest

androidx.compose.ui.test.runEmptyComposeUiTest

androidx.compose.ui.test.v2.runEmptyComposeUiTest

androidx.compose.ui.test.AndroidComposeUiTestEnvironment

androidx.compose.ui.test.v2.AndroidComposeUiTestEnvironment

Compatibilidade com versões anteriores e exceções

As APIs v1 atuais estão descontinuadas, mas continuam usando UnconfinedTestDispatcher para manter o comportamento atual e evitar mudanças importantes.

A única exceção em que o comportamento padrão mudou é a seguinte:

O agente de teste padrão usado para executar a composição na classe AndroidComposeUiTestEnvironment mudou de UnconfinedTestDispatcher para StandardTestDispatcher. Isso afeta casos em que você cria uma instância usando o construtor ou uma subclasse AndroidComposeUiTestEnvironment e chama esse construtor.

Mudança principal: impacto na execução de corrotinas

A principal diferença entre as versões v1 e v2 das APIs é como as corrotinas são despachadas:

  • APIs v1 (UnconfinedTestDispatcher): quando uma corrotina era iniciada, ela era executada imediatamente na linha de execução atual, muitas vezes terminando antes da próxima linha de código de teste ser executada. Ao contrário do comportamento de produção, essa execução imediata pode mascarar inadvertidamente problemas de tempo real ou condições de corrida que ocorreriam em um aplicativo ativo.
  • APIs v2 (StandardTestDispatcher): quando uma corrotina é iniciada, ela é enfileirada e não é executada até que o teste avance explicitamente o relógio virtual. As APIs de teste padrão do Compose (como waitForIdle()) já processam essa sincronização. Portanto, a maioria dos testes que dependem dessas APIs padrão continuará funcionando sem mudanças.

Falhas comuns e como corrigir

Se os testes falharem após o upgrade para a v2, provavelmente vão apresentar o seguinte padrão:

  • Falha: você inicia uma tarefa (por exemplo, uma ViewModel carrega dados), mas sua asserção falha imediatamente porque os dados ainda estão em um estado "Carregando".
  • Causa: com as APIs v2, as corrotinas são enfileiradas em vez de executadas imediatamente. A tarefa foi enfileirada, mas nunca executada antes da verificação do resultado.
  • Correção: avance o tempo explicitamente. É necessário informar explicitamente ao dispatcher v2 quando executar o trabalho.

Abordagem anterior

Na v1, a tarefa era iniciada e concluída imediatamente. Na v2, o código a seguir falha porque loadData() ainda não foi executado.

// In v1, this launched and finished immediately.
viewModel.loadData()

// In v2, this fails because loadData() hasn't actually run yet!
assertEquals(Success, viewModel.state.value)

Use waitForIdle ou runOnIdle para executar tarefas enfileiradas antes de fazer asserções.

Opção 1: usar waitForIdle avança o relógio até que a interface fique inativa, verificando se a corrotina foi executada.

viewModel.loadData()

// Explicitly run all queued tasks
composeTestRule.waitForIdle()

assertEquals(Success, viewModel.state.value)

Opção 2: usar runOnIdle executa o bloco de código na linha de execução de interface depois que a UI fica inativa.

viewModel.loadData()

// Run the assertion after the UI is idle
composeTestRule.runOnIdle {
    assertEquals(Success, viewModel.state.value)
}

Sincronização manual

Em cenários que envolvem sincronização manual, como quando o avanço automático está desativado, iniciar uma corrotina não resulta em execução imediata porque o relógio de teste está pausado. Para executar corrotinas na fila sem avançar o relógio virtual, use a API runCurrent(). Isso executa tarefas programadas para o tempo virtual atual.

composeTestRule.mainClock.scheduler.runCurrent()

Ao contrário de waitForIdle(), que avança o relógio de teste até que a interface se estabilize, runCurrent() executa tarefas pendentes enquanto mantém o tempo virtual atual. Esse comportamento permite a verificação de estados intermediários que seriam ignorados se o relógio fosse avançado para um estado ocioso.

O programador de testes usado no ambiente de teste é exposto. Esse scheduler pode ser usado em conjunto com a API runTest do Kotlin para sincronizar o relógio de teste.

Migrar para runComposeUiTest

Se você estiver usando APIs de teste do Compose com a API runTest do Kotlin, é altamente recomendável mudar para runComposeUiTest.

Abordagem anterior

Usar createComposeRule em conjunto com runTest cria dois relógios separados: um para o Compose e outro para o escopo da corrotina de teste. Essa configuração pode forçar você a sincronizar manualmente o agendador de testes.

@get:Rule
val composeTestRule = createComposeRule()

@Test
fun testWithCoroutines() {
    composeTestRule.setContent {
        var status by remember { mutableStateOf("Loading...") }
        LaunchedEffect(Unit) {
            delay(1000)
            status = "Done!"
        }
        Text(text = status)
    }

    // NOT RECOMMENDED
    // Fails: runTest creates a new, separate scheduler.
    // Advancing time here does NOT advance the compose clock.
    // To fix this without migrating, you would need to share the scheduler
    // by passing 'composeTestRule.mainClock.scheduler' to runTest.
    runTest {
        composeTestRule.onNodeWithText("Loading...").assertIsDisplayed()
        advanceTimeBy(1000)
        composeTestRule.onNodeWithText("Done!").assertIsDisplayed()
    }
}

A API runComposeUiTest executa automaticamente o bloco de teste no próprio escopo runTest. O relógio de teste é sincronizado com o ambiente do Compose, então não é mais necessário gerenciar o programador manualmente.

    @Test
    fun testWithCoroutines() = runComposeUiTest {
        setContent {
            var status by remember { mutableStateOf("Loading...") }
            LaunchedEffect(Unit) {
                delay(1000)
                status = "Done!"
            }
            Text(text = status)
        }

        onNodeWithText("Loading...").assertIsDisplayed()
        mainClock.advanceTimeBy(1000 + 16 /* Frame buffer */)
        onNodeWithText("Done!").assertIsDisplayed()
    }
}