Configura el entorno de pruebas con las APIs de prueba v2

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

Las APIs v1 están obsoletas y se recomienda migrar a las APIs nuevas. 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 v1 obsoletas, consulta Asignaciones de APIs.

Mientras que las APIs de la versión 1 dependían de UnconfinedTestDispatcher, las APIs de la versión 2 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 pruebas. 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 pruebas de Compose v2, que usan StandardTestDispatcher de forma predeterminada. Si tus pruebas usan las 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 las pruebas, se dejaron de usar varias sobrecargas que aceptan parámetros de configuración individuales, como effectContext, runTestContext o testTimeout. Actualiza tus pruebas para usar 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 sean táctiles y que se hayan configurado a través de las APIs de instrumentación antes de que comience la prueba. Dentro de las funciones de configuración de las pruebas, el sistema aplica InputMode.Touch de forma predeterminada al comienzo de cada prueba para lograr un mayor determinismo y evitar la filtración de estado, lo que anula el estado del dispositivo ambiental 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 de 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 y soluciones de migración, 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, por lo general, podrás usar Buscar y reemplazar para actualizar las importaciones de paquetes y adoptar los nuevos cambios del despachador.

Como alternativa, pídele a Gemini que realice una migración a la versión 2 de las APIs de pruebas de Compose con la siguiente instrucción:

Migra de las APIs de prueba v1 a las APIs de prueba 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 obsoletas a sus reemplazos de la versión 2:

Obsoleta (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 la subclase AndroidComposeUiTestEnvironment, y llamas a ese constructor.

Cambio clave: Impacto en la ejecución de corrutinas

La diferencia principal entre la versión 1 y la versión 2 de las APIs es la forma en que se despachan las corrutinas:

  • APIs 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 de producción, esta ejecución inmediata podría enmascarar de forma inadvertida problemas de sincronización reales o condiciones de carrera que ocurrirían en una aplicación en vivo.
  • APIs de la versión 2 (StandardTestDispatcher): Cuando se inicia una corrutina, se pone en cola y no se ejecuta hasta que la prueba avanza de forma explícita el reloj virtual. Las APIs de prueba estándar de Compose (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 corregirlas

Si tus pruebas fallan después de actualizar a la versión 2, es probable que muestren 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 un 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.
  • Solución: Avanza 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 inició y finalizó 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 aserciones.

Opción 1: El uso de waitForIdle hace avanzar 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: El uso de runOnIdle ejecuta el bloque de código en el subproceso de IU después de que la IU se haya inactiva.

viewModel.loadData()

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

Sincronización manual

En situaciones que implican una sincronización manual, como cuando se inhabilita el avance automático, el lanzamiento de una corrutina no genera una ejecución inmediata porque el reloj de prueba está en pausa. Para ejecutar corrutinas en la cola sin avanzar 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 avanza el reloj de prueba hasta que se estabiliza la IU, runCurrent() ejecuta las tareas pendientes mientras mantiene el tiempo virtual actual. Este comportamiento permite verificar los 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

Si usas createComposeRule junto con runTest, se crean dos relojes independientes: 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 necesitas 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()
    }
}