Cómo probar animaciones

Compose ofrece ComposeTestRule, que te permite escribir pruebas para animaciones de manera determinista con control total sobre el reloj de prueba. Esto te permite verificar los valores de animación intermedios. Además, una prueba puede ejecutarse más rápido que la duración real de la animación.

ComposeTestRule expone su reloj de prueba como mainClock. Puedes configurar la propiedad autoAdvance como "false" para controlar el reloj en tu código de prueba. Después de iniciar la animación que deseas probar, el reloj puede moverse con advanceTimeBy.

Debes tener en cuenta que advanceTimeBy no mueve el reloj exactamente según la duración especificada. En cambio, se redondea a la duración más cercana que sea multiplicador de la duración del fotograma.

@get:Rule
val rule = createComposeRule()

@Test
fun testAnimationWithClock() {
    // Pause animations
    rule.mainClock.autoAdvance = false
    var enabled by mutableStateOf(false)
    rule.setContent {
        val color by animateColorAsState(
            targetValue = if (enabled) Color.Red else Color.Green,
            animationSpec = tween(durationMillis = 250)
        )
        Box(Modifier.size(64.dp).background(color))
    }

    // Initiate the animation.
    enabled = true

    // Let the animation proceed.
    rule.mainClock.advanceTimeBy(50L)

    // Compare the result with the image showing the expected result.
    // `assertAgainGolden` needs to be implemented in your code.
    rule.onRoot().captureToImage().assertAgainstGolden()
}

Optimiza las pruebas de animación

Cuando pruebas animaciones de alta fidelidad, a menudo necesitas inhabilitar el avance automático y recorrer los fotogramas de forma manual para confirmar los estados intermedios de la IU. Para estos bucles específicos fotograma por fotograma, usa el runWithoutImplicitWait método para ejecutar tus aserciones. Las consultas de nodos estándar (como onNodeWithTag o fetchSemanticsNode) activan sincronizaciones implícitas que son redundantes cuando controlas el reloj de forma manual, por lo que omitirlas acelera significativamente los tiempos de ejecución de las pruebas.

lineamientos de uso

  • Administración manual del reloj: Usa esta API cuando mainClock.autoAdvance esté configurado como false y la IU se encuentre en un estado estable y conocido para el fotograma actual.
  • Ejecución del subproceso de IU: Para garantizar la estabilidad del árbol de la IU, llama a runWithoutImplicitWait en el subproceso de IU, como con runOnUiThread. Si lo ejecutas fuera del subproceso de IU, la prueba estará expuesta a condiciones de carrera y lecturas de estado obsoletas.
  • Aserciones de solo lectura: El bloque debe contener estrictamente aserciones de solo lectura. Cualquier acción que modifique el estado debe realizarse fuera de este bloque.

Ejemplo

@Test
fun runWithoutImplicitWaitSample() = runComposeUiTest {
    setContent { MainScreen() }
    mainClock.autoAdvance = false

    // Trigger an animation
    onNodeWithText("Start Animation").performClick()

    // Step through the animation frame-by-frame
    while (hasPendingWork()) {
        mainClock.advanceTimeByFrame()
        waitForIdle()
        runOnUiThread {
            // Suppress implicit synchronization inside this block to avoid redundant
            // waits on each node query, making the frame assertions execute much faster.
            runWithoutImplicitWait {
                val box1 = onNodeWithTag("Box1").fetchSemanticsNode()
                val box2 = onNodeWithTag("Box2").fetchSemanticsNode()
                val box3 = onNodeWithTag("Box3").fetchSemanticsNode()

                // Assert the exact intermediate state of all three properties for this frame
                assert(box1.boundsInRoot.right <= box2.boundsInRoot.left)
                assert(box2.boundsInRoot.right <= box3.boundsInRoot.left)
            }
        }
    }
}