Cómo probar ViewModels y LiveData

1. Antes de comenzar

En codelabs anteriores, aprendiste a usar elementos ViewModel a fin de controlar la lógica empresarial, además de LiveData para IU reactivas. En este codelab, aprenderás a escribir pruebas de unidades con el propósito de verificar que el código ViewModel funcione correctamente.

Requisitos previos

  • Haber creado directorios de prueba en Android Studio
  • Haber escrito pruebas de instrumentación y de unidades en Android Studio
  • Haber agregado dependencias de Gradle a un proyecto de Android

Qué aprenderás

  • Cómo escribir pruebas de unidades para elementos ViewModel y LiveData

Requisitos

  • Una computadora que tenga Android Studio instalado
  • El código de la solución de la app de Cupcake

Descarga el código de partida para este codelab

En este codelab, agregarás pruebas de instrumentación a la app de Cupcake a partir de códigos de solución anteriores.

Para obtener el código necesario para este codelab y abrirlo en Android Studio, haz lo siguiente:

Obtén el código

  1. Haz clic en la URL proporcionada. Se abrirá la página de GitHub del proyecto en un navegador.
  2. Verifica y confirma que el nombre de la rama coincida con el nombre de la rama que se especifica en el codelab. Por ejemplo, en la siguiente captura de pantalla, el nombre de la rama es main.

fe29aa9112862a93.png

  1. En esa página, haz clic en el botón Code, que abre una ventana emergente.

5b0a76c50478a73f.png

  1. En la ventana emergente, haz clic en el botón Download ZIP para guardar el proyecto en tu computadora. Espera a que se complete la descarga.
  2. Ubica el archivo en tu computadora (probablemente en la carpeta Descargas).
  3. Haz doble clic en el archivo ZIP para descomprimirlo. Se creará una carpeta nueva con los archivos del proyecto.

Abre el proyecto en Android Studio

  1. Inicia Android Studio.
  2. En la ventana Welcome to Android Studio, haz clic en Open.

a065e3d575fe607b.png

Nota: Si Android Studio ya está abierto, selecciona la opción de menú File > Open.

4f3b1e628c7695f1.png

  1. En el navegador de archivos, ve hasta donde se encuentra la carpeta de proyecto descomprimido (probablemente en Descargas).
  2. Haz doble clic en la carpeta del proyecto.
  3. Espera a que Android Studio abra el proyecto.
  4. Haz clic en el botón Run 11c34fc5e516fb1c.png para compilar y ejecutar la app. Asegúrate de que funcione como se espera.

2. Descripción general de la app de partida

La app de Cupcake consiste de una pantalla principal que muestra una pantalla de pedidos con tres opciones para las cantidades de cupcakes. Si haces clic en una opción, irás a una pantalla en la que podrás seleccionar un sabor y, luego, navegarás a otra en la que seleccionarás una fecha de retiro del pedido. Luego podrás enviar el pedido a otra app y, además, podrás cancelarlo en cualquiera de estas etapas.

3. Crea los directorios de prueba de unidades

Crea un directorio de prueba de unidades para la app de Cupcake como lo hiciste en codelabs anteriores.

4. Crea una clase de prueba de unidades

Crea una clase nueva llamada ViewModelTests.kt.

5. Agrega las dependencias necesarias

Agrega las siguientes dependencias a tu proyecto:

testImplementation 'junit:junit:4.+'
testImplementation 'androidx.arch.core:core-testing:2.1.0'

Ahora sincroniza tu proyecto.

6. Escribe una prueba de ViewModel

Comencemos con una prueba simple. Lo primero que haremos cuando interactuamos con la app en un dispositivo o emulador será seleccionar la cantidad de cupcakes. Por lo tanto, primero probaremos el método setQuantity() en el OrderViewModel y verificaremos el valor del objeto quantity LiveData.

La variable quantity, que probaremos, es una instancia de LiveData. Probar objetos LiveData requiere un paso adicional, que es donde entra en juego la dependencia que agregamos. Usaremos LiveData para actualizar la IU en cuanto cambie un valor. Nuestra IU se ejecutará en lo que llamamos el "subproceso principal". Si desconoces los subprocesos y la simultaneidad, no te preocupes: los analizaremos en profundidad en otros codelabs. Por ahora, en el contexto de una app para Android, piensa en el subproceso principal como el de IU. El código que muestra la IU a un usuario se ejecuta en este subproceso. A menos que se especifique lo contrario, en una prueba de unidades se supone que todo se ejecuta en el subproceso principal. Sin embargo, debido a que los objetos LiveData no pueden acceder al subproceso principal, debemos indicar de forma explícita que estos objetos LiveData no deben llamar a dicho subproceso.

  1. Para especificar que los objetos LiveData no deben llamar al subproceso principal, debemos proporcionar una regla de prueba específica cada vez que probemos un objeto LiveData.
@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
  1. Ahora podemos crear una función llamada quantity_twelve_cupcakes(). En el método, crea una instancia de OrderViewModel..
  2. En esta prueba, verificarás que el objeto quantity en OrderViewModel esté actualizado cuando se llame a setQuantity. Sin embargo, antes de llamar a cualquier método o trabajar con datos en OrderViewModel, es importante tener en cuenta que, cuando se prueban los valores de un objeto LiveData, se deben observar los objetos para que se emitan los cambios. Una manera sencilla de hacerlo es usar el método observeForever. Llama al método observeForever en el objeto quantity. Este método requiere una expresión lambda, pero se puede dejar en blanco.
  3. Luego, llama al método setQuantity() y pasa 12 como parámetro.
val viewModel = OrderViewModel()
viewModel.quantity.observeForever {}
viewModel.setQuantity(12)
  1. Podemos inferir de forma segura que el valor del objeto quantity es 12. Ten en cuenta que los objetos LiveData no son el valor propiamente dicho. Los valores se encuentran en una propiedad llamada value. Realiza la siguiente aserción:
assertEquals(12, viewModel.quantity.value)

La prueba debería verse de la siguiente manera:

@Test
fun quantity_twelve_cupcakes() {
   val viewModel = OrderViewModel()
   viewModel.quantity.observeForever {}
   viewModel.setQuantity(12)
   assertEquals(12, viewModel.quantity.value)
}

Ejecuta la prueba. ¡Felicitaciones! Acabas de escribir tu primera prueba de unidades de LiveData, que es una habilidad crítica en Modern Android Development. Con ella no se prueba mucha lógica empresarial, así que escribamos una un poco más compleja.

Una de las funciones principales de OrderViewModel es calcular el precio de nuestro pedido. Esto sucede cuando seleccionamos una cantidad de cupcakes y una fecha de retiro. El cálculo del precio se realiza en un método privado, por lo que nuestra prueba no puede llamar directamente a este método. Solo otros métodos en OrderViewModel pueden hacerlo. Esos métodos son públicos, por lo que los llamaremos para activar el cálculo del precio de modo que podamos verificar que el valor sea el esperado.

Prácticas recomendadas

El precio se actualiza cuando se selecciona la cantidad de cupcakes y la fecha. Si bien deberían probarse ambas opciones, suele ser preferible realizar pruebas para una sola funcionalidad. Por lo tanto, crearemos métodos separados para cada prueba: una para probar el precio cuando se actualice la cantidad y otra para probarlo cuando se actualice la fecha. No queremos que falle el resultado de una prueba a causa de una prueba fallida diferente.

  1. Crea un método llamado price_twelve_cupcakes() y anótalo como una prueba.
  2. En él, crea una instancia del OrderViewModel, llama al método setQuantity() y pasa 12 como un parámetro.
val viewModel = OrderViewModel()
viewModel.setQuantity(12)
  1. Si observas el PRICE_PER_CUPCAKE en OrderViewModel, podemos ver que los cupcakes tienen un valor de USD 2.00 cada uno. También podemos ver que se llama a resetOrder() cada vez que se inicializa ViewModel; en este método, la fecha predeterminada es la de hoy y el PRICE_FOR_SAME_DAY_PICKUP es USD 3.00. Por lo tanto, 12 * 2 + 3 = 27. Se espera que el valor de la variable price, después de seleccionar 12 cupcakes, sea de USD 27.00. Declaremos que nuestro valor esperado de USD 27.00 equivale al valor del objeto price LiveData.
assertEquals("$27.00", viewModel.price.value)

Ahora, ejecuta la prueba.

Debería fallar.

17c8a24e4d7d635d.png

El resultado de la prueba indica que nuestro valor real era null. Hay una explicación para esto. Si observas la variable price en OrderViewModel, verás lo siguiente:

val price: LiveData<String> = Transformations.map(_price) {
   // Format the price into the local currency and return this as LiveData<String>
   NumberFormat.getCurrencyInstance().format(it)
}

Este es un ejemplo de los motivos por el que LiveData debería observarse durante las pruebas. El valor de price se establece mediante Transformation. En esencia, este código toma el valor que asignamos a price y lo transforma a un formato de moneda de modo que no tengamos que hacerlo de forma manual. Sin embargo, este código tiene otras implicaciones. Cuando se transforma un objeto LiveData, no se llama al código a menos que sea absolutamente necesario; esto ahorra recursos en un dispositivo móvil. Solo se llamará al código si observamos el objeto en busca de cambios. Desde luego, esto se hace en nuestra app, pero debemos hacer lo mismo para la prueba también.

  1. En el método de prueba, agrega la siguiente línea antes de configurar la cantidad:
viewModel.price.observeForever {}

La prueba debería verse de la siguiente manera:

@Test
fun price_twelve_cupcakes() {
   val viewModel = OrderViewModel()
   viewModel.price.observeForever {}
   viewModel.setQuantity(12)
   assertEquals("$27.00", viewModel.price.value)
}

Ahora, si ejecutas la prueba, debería ser exitosa.

7. Código de solución

8. Felicitaciones

En este codelab, aprendimos a hacer lo siguiente:

  • Configurar una prueba LiveData
  • Probar LiveData
  • Probar LiveData que se transformó
  • Observar LiveData en una prueba de unidades