APIs de Compose test 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 quedaron obsoletas, 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+.

Mientras que 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.

Configura el entorno de pruebas

Las APIs de Compose Test v2 usan ComposeUiTestConfig para personalizar el entorno de prueba. Las APIs que crean funciones de configuración para pruebas, como createComposeRule, runComposeUiTest y otras APIs relacionadas, aceptan ComposeUiTestConfig. Este objeto de configuración consolida las APIs relacionadas con el entorno, como effectContext, runTestContext y testTimeout, en un solo objeto.

El modelo de configuración también administra inputMode. Las APIs de Compose Test v2 aplican InputMode.Touch de forma predeterminada al comienzo de cada prueba para garantizar el determinismo y evitar que el estado del modo de entrada se filtre entre las pruebas.

ComposeUiTestConfig forma parte de las APIs de Compose Test v2, que usan StandardTestDispatcher de forma predeterminada. Si tus pruebas usan APIs de v1, consulta Migra a las APIs de prueba de v2 antes de adoptar ComposeUiTestConfig.

Migra a ComposeUiTestConfig

Dentro de las sobrecargas para crear funciones de configuración en pruebas, se dejaron de usar varias sobrecargas que aceptan parámetros de configuración individuales, como effectContext, runTestContext o testTimeout. Actualiza tus pruebas para que usen ComposeUiTestConfig, como se muestra en el siguiente ejemplo:

Modo de entrada predeterminado

Las pruebas pueden fallar durante la migración si dependen de modos de entrada que no son táctiles y que se configuran a través de las APIs de instrumentación antes de que comience la prueba. Dentro de las funciones de configuración para las pruebas, el sistema aplica InputMode.Touch de forma predeterminada al comienzo de cada prueba para lograr un mayor determinismo y evitar la pérdida de estado, lo que anula el estado del dispositivo ambiente y la configuración previa a la prueba.

Para resolver este problema, especifica el modo de entrada requerido en ComposeUiTestConfig:

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

    @Test
    fun testFocus() {}
}

Para configurar el modo de entrada para casos de prueba individuales en lugar de toda la clase de prueba, pasa ComposeUiTestConfig a 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 conocer otros problemas de migración y sus soluciones, consulta Fallas comunes y cómo corregirlas.

Migra a las APIs de prueba de la versión 2

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 potenciadas por 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 v1 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 en vivo.
  • 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: Inicias una tarea (por ejemplo, un ViewModel carga datos), pero tu aserción falla de inmediato porque los datos aún están en estado de "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(). 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 se estabiliza la IU, 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

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