¿Cómo empezar con Flutter Testing? — Parte uno —

Ana Polo
Flutter España
Published in
9 min readNov 15, 2021

¡Hola! 👋

Hace unos días empecé mi viaje a través de Flutter testing. Este mundo era desconocido para mi pero sabía que aplicar testing a nuestras aplicaciones es super importante y por ello decidí aprenderlo por mi cuenta.

Probablemente el código que te voy a enseñar ahora mismo podría estar mejor, pero recuerda, es mi primera vez haciendo flutter testing y me gustaría enseñarte que es lo que hice. Si tienes alguna sugerencia el código será open-source.

Mi idea con este repositorio es tener un lugar para que toda persona interesada en aprender flutter testing pueda aprender y colaborar creando nuevas PRs, issues, etc.

¡Empezamos! 🙌

Lo primero que quiero explicar es el tipo de proyecto que he creado.

He creado una app que muestra gatos random gracias a una API de gatos. En este caso he creado un bottom navigation widget que contendrá tres páginas. En este articulo nos centraremos en la primera página.

Random cats🐱

Esta página es un visualizador de gatos random, tendrás el poder de ver gatos de diferentes partes del mundo con un simple click, increíble, ¿verdad?.😂

Bromas aparte, la idea de esta página es permitir al usuario visualizar gatos de manera random cuando pulsa en el botón flotante (Floating action button).

Para obtener ese resultado he estructurado la app de manera simple. Este articulo no hablará sobre arquitectura por lo que he decidido centrarme solo en el objetivo de este articulo: testing. 🧪

De todos modos, esta es la estructura de carpetas de la aplicación.

Caso de éxito

En este flujo, el usuario hará click en el FAB y esté llamará al evento de bloc. Bloc será el encargado de comunicarse con el repository. El repository llamará al servicio y este llamará a la API que obtendrá el siguiente gato. En este caso el flujo es correcto por lo que el servicio devolverá un objeto de tipo Cat al repository y este se lo devolverá al bloc.

Después, bloc comprobará si el gato es correcto (comprobará si tiene un listado de razas que no esté vacío o sea null ya que es necesario para pintar datos en la vista) y emitirá el estado RandomCatStatus.success.

La vista estará escuchando gracias al BlocConsumer y si este estado ocurre repintará la vista con el nuevo gato.

Caso de éxito con lista de razas vacía o null.

En este caso, el servicio retorna una respuesta correcta pero el objeto Cat tiene la lista de razas (breeds) vacía o null. Como he mencionado con anterioridad esta lista es necesaria para mostrar información en la vista. Por lo tanto, si bloc comprueba que esto ocurre emitirá un estado RandomCatStatus.emptyBreed.

Este estado se estará escuchando en el listener del BlocConsumer y cuando ocurra se volverá a llamar al evento para obtener el siguiente gato.

Lanzar error cuando el response.body es vacío.

Este es otro caso que podría ocurrir, cuando el servicio responde con un código 200 pero la respuesta es vacía, en este caso, el servicio lanzará un error.

Cuando este error es lanzado el bloc lo capturará gracias al try/catch y emitirá un estado RandomCatStatus.failure.

La vista estará escuchando este estado y mostrará un mensaje de error.

Lanzar error cuando el response.statusCode no es 200.

Por ejemplo, si el código de respuesta retorna un 404 o un 400 la app lanzará un error. Bloc lo capturará y emitirá el estado RandomCatStatus.failure para que la vista muestre un mensaje de error.

Estos son todos los posibles casos para la aplicación.

¡Vamos al testing ! 🧪

Es la hora de comenzar con flutter testing.

La primero que necesitamos hacer es tener las dependencias correctas dentro de nuestro pubspec.yaml.

¡Bien!, ahora podemos comenzar ✅

Dentro de la raíz del proyecto → carpeta test, he creado esta estructura:

En flutter podemos hacer diferentes tipos de testing:

  • Unit testing
  • Bloc testing
  • Widget testing
  • Integration testing

Aquí nos vamos a centrar en los tres primeros.

Cuando estamos haciendo testing podemos usar diferentes métodos antes o después. A continuación te los muestro:

Unit test

En esta parte voy a testear los métodos que he creado en las clases de service y repository.

Esta es la clase service.dart.

En este caso tengo que testear todos los posibles escenarios que el servicio puede tener. Para hacerlo, lo primero que necesito es crear las classes mock para la response, los objetos, etc.

Después dentro del método main, he creado un grupo para testear todo lo relacionado con la clase de servicio. En el método setUpAll , he añadido registerFallbackValue(FakeUri()), ya que es necesario para usar una URI falsa en nuestros test. Si no hacemos esto no podremos usar el método any().

Además, he creado las variables necesarias para los mocks. Estas se deben instanciar en el método setUp.

Adicionalmente, he creado una variable JSON en una clase separada para tener una respuesta falsa para añadir a los resultados de la API (para cuando tenga que devolver un gato correcto).

El primer test que voy a realizar es comprobar si el constructor no requiere un httpClient.

Ahora voy a crear un grupo para tener todos los test relacionados con obtener un gato de forma random.

group(('catSearch'), () {});

Dentro de este grupo, necesito comprobar diferentes cosas.

  1. Cuando el servicio hace una petición http correcta pero el body es vacío.

2. Cuando el servicio lanza un error al responder con un código diferente a 200.

3. Cuando el servicio retorna un objeto Cat válido.

Si ejecutas todos estos test obtendrás la mejor frase de todas: ¡todos los test han pasado!

Esta es la clase completa.

Ahora es momento de testear la clase repository.

Esta es la clase cat_respository.

Lo primero que hice fue crear los mocks necesarios.

Lo segundo, he creado un grupo para tener todos los test relacionados con esta clase. Aquí he creado las variables necesarias y las he inicializado dentro del método setUp.

Lo siguiente que he hecho es crear un test para el constructor, aquí he comprobado que la clase cat service es requerida.

Otras comprobaciones para obtener toda la cobertura de test:

  1. Llamar al método search.

2. Lanzar error cuando el método search falla.

Este es el código generado en la clase cat_repository_test.dart.

¡Todos los tests han pasado!

¡Perfecto! 😎 Ahora que hemos pasado todos los test de nuestras clases service y repository vamos a comenzar con la siguiente parte, bloc testing.

Bloc Test

Esta es la clase random_cat_bloc_test.dart.

Este tipo de testing tiene la sintaxis un poco diferente a los unit test pero tienen la misma filosofía.

Esta es la documentación oficial para realizar test en bloc: Bloc State Management Library. He seguido esta documentación y he conseguido ejecutar mis propios bloc tests.

¡Comencemos! 🙌

Cómo siempre, el primer paso que necesitamos hacer es crear las clases mock necesarias.

Lo siguiente que necesitamos realizar es crear e inicializar las variables para nuestros mocks. Además, he creado un test inicial para comprobar si el estado inicial de nuestro bloc es RandomCatStatus.initial.

Ahora podemos empezar a testear nuestro bloc.

  1. Emitir RandomCatStatus.loading y RandomCatStatus.emptyBreeds cuando la lista de razas es vacía.

2. Emitir RandomCatStatus.loading y RandomCatStatus.emptyBreeds cuando la lista de razas es nula.

3. Emitir RandomCatStatus.loading y RandomCatStatus.success cuando la respuesta es correcta ya que el gato tiene listado de razas válido.

4. Emitir RandomCatStatus.loading y RandomCatStatus.failure cuando la respuesta no es válida.

Esta es la clase random_cat_bloc_test.

También es necesario hacer test a nuestras clases event y state.

Esta es la clase random_cat_state_test.dart.

Y esta es la clase random_cat_event_test.dart.

¡Todos los test han pasado!

Ahora que hemos terminado con la parte de unit test y bloc test comenzaremos con la parte de Widget test.

Widget testing

Este tipo de testing es genial ya que puedes controlar todas las posibles interacciones del usuario con la aplicación con lo que puedes tener el control absoluto de todos los estados de tu app.

En este caso los test de widget serán sencillos.✌️

¡Comencemos!

Lo primero que necesitamos es testear nuestra clase random_cat_page.

Ahora crearemos nuestras clases fake.

Después necesitaremos registrarlas dentro del método setUpAll.

En este caso solo necesitamos testear que la página se ha pintado correctamente.

Esta es la clase completa.

¡Todos los test han pasado correctamente!

La siguiente clase que necesitamos testear es: random_cat_layout_test.dart.

Aquí he creado diferentes tipos de vistas dependiendo del estado de la vista. Para testear estas vistas la parte más importante es añadir un Key.

Vistas según estados.

En esta clase se deben realizar diferentes tipos de tests, como por ejemplo, la interacción del botón y los estados, así que vamos con ello.

Lo primero que necesitamos es nuestras clases mock y fake.

Después tenemos que crear e inicializar las variables.

Una vez inicializadas lo que tendremos que hacer es testear todos los posibles estados de la app, para ello he creado un grupo especifico para albergar todos estos casos.

group('RandomCatPage states ', () {});
  1. Renderizar la vista FailureRandomCatView cuando el estado es RandomCatStatus.failure.

2. Renderizar la vista LoadingView cuando el estado es RandomCatStatus.loading.

3. Renderizar la vista SuccessRandomCatView cuando el estado es RandomCatStatus.success.

4. Renderizar la vista InitialRandomCatView cuando el estado es RandomCatStatus.initial.

¡Genial! 😎 Ahora tenemos testeados todos los posibles estados para esta vista, podemos continuar con el siguiente paso: ¡Testear el click al botón!.

  1. Llamar al evento SearchRandomCat cuando se pulsa el botón.

Finalmente necesitamos controlar la parte en la que el gato no tiene un listado de razas válido. Cuando esto pasa el BlocConsumer se encargará de escuchar al estado y volver a llamar al evento SearchRandomCat.

Esta es la clase completa.

¡Todos los test han pasado!

El último paso que necesitamos hacer es comprobar el porcentaje de cobertura de nuestros tests. Para hacerlo tenemos diferentes opciones:

  1. Ejecutar flutter test — coverage en un terminal.
  2. Instalar LCOV para visualizarlos en HTML

He usado la segunda opción, así que voy a enseñarte como instalarlo y usarlo (Instalación para Mac).

  1. brew install lcov
  2. flutter test — coverage
  3. genhtml coverage/lcov.info -o coverage/html

Y por fin podrás ver el 100% de cobertura de test en la aplicación. 🧪✌️

Conclusión

Creo que aprender testing al principio puede ser duro y frustrante pero mi consejo es que deberías aprenderlo ya que si quieres crear buenas aplicaciones necesitarás comprender como funcionan los tests. Es la primera vez que realizo tests en una aplicación flutter y he comprendido lo importantes que son ya que incluso creando una app simple como esta me han hecho corregir varios bugs.

Así que por favor… ¡haz tests!😊

Repositorio en Github

Como he mencionado al principio del articulo, me gustaría que este proyecto sea open-source, así que siéntete libre de añadir nuevo contenido, PRs, issues o lo que consideres necesario.

Espero que este repositorio sea de ayuda para quienes estén interesaos en aprender flutter testing de una manera más interactiva.

Github: https://github.com/AnnaPS/cats_flutter_bloc

¡Gracias por leer el articulo!

Dato curioso: He cogido la base del proyecto del capitulo 3 del libro Flutter apprentice! Si no sabes lo que es, te recomiendo que accedas a este link: https://www.raywenderlich.com/books/flutter-apprentice/v1.0.ea2

Gracias a Very Good Ventures Team por el curso de conceptos básicos de testing https://www.youtube.com/watch?v=M_eZg-X789w&list=PLprI2satkVdFwpxo_bjFkCxXz5RluG8FY.

También agradecer a Felix Angelov por todos los ejemplos que hay en el repositorio de la librería de bloc: https://github.com/felangel/bloc/tree/master/examples .

Especial agradecimiento a Oscar Martin por ayudarme con la parte de test 😊.

Puedes encontrarme en Twitter: AnaPolo_dev .😊

Si te ha gustado el articulo puedes apoyar el contenido con aplausos. 👏

--

--

Ana Polo
Flutter España

Software Engineer at Very Good Ventures. 🦄 Co-Organizer of @FlutterMadrid & @flutter_es communities. The organizer of @es_flutteristas. Github: AnnaPS 💙