Testing de ViewControllers en iOS

Escribimos mucho código de UI, tanto los ViewController como las UIView constituyen gran parte de nuestro código base. 

Además hay muchos casos extremos que tenemos que manejar al testear nuestras vistas.

Es poco habitual hacer pruebas sobre las Views, ya que generalmente se prueba la lógica de negocio, Casos de Uso, Repositorios, etc. relegando los test de UI.

Pero por qué?

Debo reconocer que es muy difícil cubrir todo el código de UI que tienes con tests unitarios.

Es mucho más  fácil probar lógica del negocio, pero probar vistas siempre me pareció algo no tan intuitivo. 

Hay varias formas de hacer tests sobre nuestras vistas:

  • Unit Test a cada elemento de la UI verificando su contenido 🤔
  • End to End Tests (XCUI) 🙈
  • SnapShot Tests 📸

Probar los componentes de la interfaz de usuario a menudo es complicado porque hay demasiadas partes móviles involucradas.

Para poder probar el controlador de vista, necesitamos que las cosas funcionen de forma aislada.

Un objetivo general del diseño es tener una clara separación de intereses.

Debemos recordar que el único trabajo de nuestras Views debe ser renderizar la UI y propagar las interacciones del usuario.

Un View controller que hace demasiadas cosas será un VC muy difícil de probar. Los patrones como MVVM, MVP ayudan en este caso.

Haciendo los View Controllers Testables

Generalmente si usamos MVVM o MVP o algún otro patrón de arquitectura al testear la salida de nuestros ViewModels indirectamente ya estamos probando la entrada de nuestras vistas. Y dado que las vista de mantienen como agentes pasivos, me parece un doble esfuerzo probar las vistas verificando el contenido de cada elemento.

La Inyección de dependencia es una técnica altamente difundida en el mundo iOS, esta nos permite aislar en este caso a nuestras Vistas para que durante testing podamos mockear los objetos.

Ejemplo usando MVVM:

Diseñar los ViewController para que dependan de un protocolo en vez de una instancia en concreta del viewModel:

Definiendo un protocolo del viewModel:

Listo ahora podemos utilizar las interface del ViewModel Protocol y mockearlo para hacer el siguiente tipo de tests:

Snapshot Test 📸

Me concentraré en este tipo de test ya que me pareció genial este framework. Originalmente creado por Facebook, pero ahora es mantenido por el equipo de Uber.

Cómo funciona?

Prueba la interface de tu app tomando snapshot de la UI y comparándola con una imagen de referencia, tan simple como eso.

Es una herramienta muy útil en nuestro arsenal de testing para hacer que la UI luzca como pretendíamos.

Si bien tiene más características como probar UIViews independientes o probar Layers, pero por ahora dejémoslo en lo básico.

Mockeando el ViewModel

Creando un SnapShot Test

Resultado:

Que pasa si sucede un error, y si la vista está cargando una siguiente página? Y si el servicio no retorna ningún elemento?

La primera vez que genero los snapshots debo ejecutar los test con la variable recordMode = true:

Para verificar las snapshots vuelvo a ejecutar con la variable recordMode en false o comentada:

Ahora todos los Test Pasan

Y si se modifica la UI?

Qué pasa por ejemplo que se modifica el renderizado de la tabla y se omite o modifica algún campo. Adrede modifico el nombre del TVShow y el color de un label en la celda.

En este caso nuestros test empiezan a fallar:

En el folder de referencia nos genera 3 archivos por cada test fallido:

Nos genera la Imagen Original que sirve como referencia, el nuevo snapshot creado y la diferencia entre estos.

Imagen original, Nueva imagen y Diferencias

Generalmente puedes manejar escribiendo assertions, pero son difíciles de visualizar, en cambio al usar este framework, puedes ver al instante exactamente qué está pasando solo mirando la captura.

Resumiendo

Espero que empiezan a usar Snapshot test, me parece una potente herramienta para empezar a probar nuestros ViewControllers y Views.

Nos permite reducir la posibilidad de introducir cambios inesperados dentro del código, lo cual es enorme para los negocios.

Los snapshot tests también son útiles durante la etapa de desarrollo. Es mucho más fácil preparar instantáneas para todos los dispositivos y casos que ejecutar cada configuración en un simulador.

Referencias:

https://github.com/uber/ios-snapshot-test-case

https://www.objc.io/issues/1-view-controllers/testing-view-controllers/

https://www.raywenderlich.com/5043-ios-snapshot-test-case-testing-the-ui

https://www.vadimbulavin.com/unit-testing-view-controller-uiviewcontroller-and-uiview-in-swift/