Existen dos estrategias principales de testing cuando estamos desarrollando una app cliente y queremos realizar test de integración o de aceptación aislandonos del servidor:
- Mock server o sustituir el servidor real
- Dobles de test o sustituir los adaptadores encargados de realizar las llamadas de red
Vamos a ver en que consisten cada una de estas estrategías desde un punto agnóstico de cualquier tecnología o librería, sin entrar en detalle en ninguna implementación ya que lo que me interesa es tratar el tema un nivel más abstracto.
Hay algunos factores clave que nos pueden hacer decidirnos por una u otra estrategía independientemente de la tecnología que estemos usando.
Mock server
Mock Server o http stubbing es una estrategia en la cual vamos a sustituir las respuestas reales del servidor por respuestas preparadas.
Existen varias opciones pueden depender de la tecnología que usemos y las librerías disponibles.
Mock server reemplazando el servidor remoto
Con esta opción vamos a tener un servidor mock donde tendremos respuestas preparadas para ciertas peticiones al servidor.
Si es un servidor mock local, se levanta y se encolan las respuestas necesarias para cada test.
Si es un servidor mock remoto o algo similar deberás tener unas respuestas preparadas estáticas para cada petición.
Nunca he utilizado la opción de tener un servidor mock remoto, en los proyectos donde he utilizado mock server, me ha resultado más flexible poder crear diferentes respuestas preparadas para un mismo endpoint en cada test usando un mock server local.
Aunque no lo he usado nunca se que postman tiene algo para crear mock servers remotos.
Otro punto a favor de utilizar mock server local, es que te ahorras tener que salir a red, haciendo los test más rápidos.
En estos artículos que te dejo a continución hablé sobre como relizar test de integración de un cliente API rest utilizando esta estrategía en kotlin multiplatform o flutter:
- Cómo crear un cliente API REST y sus test de integración en flutter
- Cómo crear un cliente API REST y sus test de integración en kotlin multiplatform
Interceptando las llamadas de red
Existe otra posibilidad que es interceptar las peticiones de red.
En esta opción no se levanta un servidor local sino que vamos a interceptar las llamadas de red, devolviendo respuestas preparadas para ciertos endpoints en cada test.
En desarrollo web, he utilizado en alguna ocasión esta opción cuando he utilizado Cypress.
También he usado interceptores utilizando la librería msw cuando he realizado test de componentes en ReacJS.
Dobles de test
Dobles de test es una estrategía donde para crear escenarios de prueba nos vamos a basar en la arquitectura testable de nuestra aplicación.
Vamos a reemplazar las dependencias de la capa de datos, ya sean repositorios, gateways o como lo hayas llamado por dobles de test para devolver datos de prueba preparados.
Para crear dobles de test podemos utilizar librerías que existen en cada tecnología o crearlos manualmente.
En este artículo habló más en profundidad sobre los tipos de dobles de test:
https://xurxodev.com/dobles-de-test/
Es importante destacar que para poder realizar esta estrategía la arquitectura de tu aplicación debe ser testable.
Para ser una arquitectura testable debe estar desacoplada de la capa de datos ocultando detalles de implementación y haciendo uso de algún sistema de inyección de dependencias ya sea mediante IOCContainer o Service Locator.
Algunas arquitecturas que encajan con este tipo de test son:
- Clean architecture
- Hexagonal Architecture
Esta opción es la ideal para tener la posibilidad de tener una mayor granularidad de testing y poder decidir aplicarla donde nos aporte mayores beneficios, pero es la más compleja y no se puede realizar en todos los casos.
Teniendo este tipo de arquitectura vamos a poder escribir tests de aceptación o test de los casos de uso utilizando dobles de test.
No tengo ningún artículo en el blog utilizando dobles de test, en test de aceptación, para reemplazar adaptadores de la capa de datos pero te dejo este de Karumi en Android para que te hagas una idea:
https://blog.karumi.com/world-class-testing-development-pipeline-for-android-part-4/
Si te puedo dejar código open source que tengo realizando este tipo de testing:
https://github.com/xurxodev/karate-stars-app/blob/master/test/widget_tests/home_news_test.dart
¿Qué estrategia utilizar?
Hay diferentes factores que limitan las opciones disponibles o sea más recomendable que optes por una de las opciones.
No tengo arquitectura testable
Este caso es el más habitual que me suelo encontrar en empresas donde voy a dar formación.
Los escenarios suelen ser:
- Han tenido malas experiencias con su cultura actual de desarrollo y quieren empezar a crear tests y no tienen claro como empezar.
- Se quiere hacer un refactor grande de la aplicación, ya sea a nivel de funcionalidad o de arquitectura y no se dispone de arquitectura testable ni de tests.
- Se quiere migrar de una tecnología a otra.
Estos escenarios suelen tener en común que no tienen una arquitectura testable y tampoco tienen tests.
Claramente la opción aquí es optar por crear tests todo terreno usando mock server ya sea utilizando mock server o mediante interceptores en función de las librerías disponibles en la tecnología utilizada.
Con este tipo de tests desde la interfaz de usuario testearemos tanto la integración con el servidor como la aceptación del cliente.
Tengo arquitectura testable pero no tengo tests
Este escenario puede ocurrir si vas a desarollar una app y vas a utilizar una arquitectura testable.
En este escenario depende del tipo de aplicación que estes desarrolando y los tests que quieras realizar, la opción más recomendada puede variar.
Sepando tests por responsabilidad
En esta estrategía aprovechando que se dispone de una arquitectura testable es posible crear test por responsabilidad.
De esta forma podemos realizar por separado los siguientes tests:
- Test de la integración del servidor
- Test de la lógica de dominio
- Test de aceptación reemplazando el servidor real
Test de la integración del servidor
En este tipo de test al ser los de más bajo nivel y más cercanos a las llamadas de red, la única opción posible es mock server, ya sea usando mock server local o remoto o interceptores.
Vamos a poder realizar unos test de granularidad muy fina, testeando que nuestra capa de datos se comporta como se espera ante respuestas concretas válidas y también inválidas como errores 404, 500 etc..
Test de la lógica de dominio
Este tipo de tests puede englobar:
- Lógica pura de negocio mediante las entidades de dominio que no requiera llamadas de red.
- Casos de uso sin lógica y exclusivamente consiste en una llamada a adaptadores de la capa de datos
- Casos de uso, con lógica, validaciones y llamadas llamadas a adaptadores de la capa de datos.
Las entidades tienen la lógica encapsulada y sin necesidad de llamadas de red y realizariamos test unitarios sin usar mock server, ni dobles de test.
En el caso de casos de uso sin lógica, desde mi punto de vista no merece la pena crear test específicos, serán testeados indirectamente en los test de aceptación.
En el caso de casos de uso con lógica y que queramos testear de forma explícita, la mejor estrategía va a depender del tipo de aplicación.
Si tu aplicación tiene un sistema de cache de datos como puede ser una aplicación mobile offline first, en este caso la mejor opción es utilizar dobles de test.
Esto es debido a que si utilizamos mock server, la cache podría estar jugándonos malas pasadas alterando las respuestas preparadas para los tests devolviendo datos cacheados que no nos interesan, cuando lo que queremos testear no tiene nada que ver con la cache.
Si queremos testear la cache podemos hacer otro tipo de test de los repositorios.
Test de aceptación reemplazando el servidor real
Este escenario es similar a los casos de uso con lógica en cuanto a la estrategia a utilizar.
Si tu aplicación tiene cache de datos, en este caso la mejor opción es utilizar dobles de test.
Si es por ejemplo una aplicación web React o Vue sin cache de datos, podemos elegir mock server o reemplazar los adaptadores de la capa de datos mediante dobles de test, lo que nos encaje mejor.
Tests todo terreno
Hay veces que aún teniendo una arquitectura testable existen motivos para no querer tener una gran granularidad de tests por responsabilidad de capas, aunque es menos habitual:
- Por temas de tiempo
- Conocimiento del equipo
- Por ir poco a poco
Es posible empezar por este tipo de tests todo terreno donde vamos a probar la integración con el servidor y la aceptación del cliente todo en uno y posteriormente ir granuralizando los test en un futuro.
Para este tipo de tests todo terreno la única opción disponible es utilizar mock server, ya que también queremos probar la integración con el servidor, en alguna de las variantes vistas al igual que cuando no se dispone de un arquitectura testable.
Es importante remarcar que si tu aplicación dispone de algún tipo de cache de datos como una aplicación mobile offline first, este tipo de tests te puede dar algún quebradero de cabeza.