Migra a las APIs de prueba de v2

Las versiones 2 de las APIs de prueba de Compose (createComposeRule, createAndroidComposeRule, runComposeUiTest, runAndroidComposeUiTest, etc.) ya están disponibles para mejorar el control sobre la ejecución de corrutinas. Esta actualización no duplica toda la superficie de la API, sino que solo se actualizaron las APIs que establecen el entorno de prueba.

Las APIs de la versión 1 están obsoletas, por lo que se recomienda migrar a las nuevas APIs. La migración verifica que tus pruebas se alineen con el comportamiento estándar de las corrutinas y evita problemas de compatibilidad futuros. Para obtener una lista de las APIs de la versión 1 que dejaron de estar disponibles, consulta Asignaciones de APIs.

Estos cambios se incluyen en androidx.compose.ui:ui-test-junit4:1.11.0-alpha03+ y androidx.compose.ui:ui-test:1.11.0-alpha03+.

Si bien las APIs de v1 se basaban en UnconfinedTestDispatcher, las APIs de v2 usan StandardTestDispatcher de forma predeterminada para la composición en ejecución. Este cambio alinea el comportamiento de las pruebas de Compose con las APIs de runTest estándar y proporciona un control explícito sobre el orden de ejecución de las corrutinas.

Mapeados de API

Cuando actualices a las APIs de la versión 2, en general, puedes usar Buscar y reemplazar para actualizar las importaciones de paquetes y adoptar los nuevos cambios del dispatcher.

También puedes pedirle a Gemini que realice una migración a la versión 2 de las APIs de prueba de Compose con la siguiente instrucción:

Migra de las APIs de prueba de v1 a las de v2

Esta instrucción usará esta guía para migrar a las APIs de prueba de la versión 2.

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

Cómo usar instrucciones de IA

Las instrucciones de IA están diseñadas para usarse en Gemini en Android Studio.

Obtén más información sobre Gemini en Studio aquí: https://developer.android.com/studio/gemini/overview

Usa la siguiente tabla para asignar las APIs de la versión 1 que dejaron de estar disponibles a sus reemplazos en la versión 2:

Obsoleto (v1)

Reemplazo (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

Retrocompatibilidad y excepciones

Las APIs de la versión 1 existentes ahora están obsoletas, pero siguen usando UnconfinedTestDispatcher para mantener el comportamiento existente y evitar cambios rotundos.

La siguiente es la única excepción en la que cambió el comportamiento predeterminado:

El despachador de pruebas predeterminado que se usa para ejecutar la composición en la clase AndroidComposeUiTestEnvironment cambió de UnconfinedTestDispatcher a StandardTestDispatcher. Esto afecta los casos en los que creas una instancia con el constructor o una subclase de AndroidComposeUiTestEnvironment y llamas a ese constructor.

Cambio clave: Impacto en la ejecución de corrutinas

La principal diferencia entre las versiones 1 y 2 de las APIs es la forma en que se envían las corrutinas:

  • APIs de v1 (UnconfinedTestDispatcher): Cuando se iniciaba una corrutina, se ejecutaba de inmediato en el subproceso actual y, a menudo, finalizaba antes de que se ejecutara la siguiente línea de código de prueba. A diferencia del comportamiento en producción, esta ejecución inmediata podría enmascarar inadvertidamente problemas de sincronización reales o condiciones de carrera que ocurrirían en una aplicación activa.
  • APIs de v2 (StandardTestDispatcher): Cuando se inicia una corrutina, se pone en cola y no se ejecuta hasta que la prueba avanza explícitamente el reloj virtual. Las APIs de prueba de Compose estándar (como waitForIdle()) ya controlan esta sincronización, por lo que la mayoría de las pruebas que dependen de estas APIs estándar deberían seguir funcionando sin cambios.

Fallas comunes y cómo solucionarlas

Si tus pruebas fallan después de actualizar a la versión 2, es probable que presenten el siguiente patrón:

  • Falla: Lanzaste una tarea (por ejemplo, un ViewModel carga datos), pero tu aserción falla de inmediato porque los datos aún están en estado "Cargando".
  • Causa: Con las APIs de la versión 2, las corrutinas se ponen en cola en lugar de ejecutarse de inmediato. La tarea se puso en cola, pero nunca se ejecutó antes de que se verificara el resultado.
  • Corrección: Se adelanta el tiempo de forma explícita. Debes indicarle de forma explícita al despachador de la versión 2 cuándo ejecutar el trabajo.

Enfoque anterior

En la versión 1, la tarea se iniciaba y finalizaba de inmediato. En la versión 2, el siguiente código falla porque loadData() aún no se ejecutó.

// 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)

Usa waitForIdle o runOnIdle para ejecutar tareas en cola antes de realizar la aserción.

Opción 1: Usar waitForIdle adelanta el reloj hasta que la IU esté inactiva, lo que verifica que se haya ejecutado la corrutina.

viewModel.loadData()

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

assertEquals(Success, viewModel.state.value)

Opción 2: Usar runOnIdle ejecuta el bloque de código en el subproceso de IU después de que la IU queda inactiva.

viewModel.loadData()

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

Sincronización manual

En situaciones que involucran la sincronización manual, como cuando se inhabilita el avance automático, iniciar una corrutina no genera una ejecución inmediata porque el reloj de prueba está en pausa. Para ejecutar corrutinas en la cola sin adelantar el reloj virtual, usa la API de runCurrent(). Esto ejecuta las tareas programadas para la hora virtual actual.

composeTestRule.mainClock.scheduler.runCurrent()

A diferencia de waitForIdle(), que adelanta el reloj de prueba hasta que la IU se estabiliza, runCurrent() ejecuta las tareas pendientes y mantiene el tiempo virtual actual. Este comportamiento permite la verificación de estados intermedios que, de lo contrario, se omitirían si el reloj avanzara a un estado inactivo.

Se expone el programador de pruebas subyacente que se usa en el entorno de pruebas. Este programador se puede usar junto con la API de runTest de Kotlin para sincronizar el reloj de prueba.

Migra a runComposeUiTest

Si usas las APIs de prueba de Compose junto con la API de runTest de Kotlin, te recomendamos que cambies a runComposeUiTest.

Enfoque anterior

El uso de createComposeRule junto con runTest crea dos relojes separados: uno para Compose y otro para el alcance de la corrutina de prueba. Esta configuración puede obligarte a sincronizar manualmente el programador de pruebas.

@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()
    }
}

La API de runComposeUiTest ejecuta automáticamente tu bloque de prueba dentro de su propio alcance de runTest. El reloj de prueba se sincroniza con el entorno de Compose, por lo que ya no es necesario administrar el programador de forma manual.

    @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()
    }
}