Mountebank – Customizando la configuración

0
1486

Con este doy comienzo a una serie de artículos sobre Mountebank, como herramienta que nos va a permitir generar “test doubles” para mockear las llamadas de nuestras APIs, el objetivo que se persigue con esta serie de artículos es aprender a utilizar de la forma más práctica posible la herramienta de virtualización de servicios Mountebank con todas sus características y particularidades.

Aquí tienes la serie completa de tutoriales sobre Mountebank:

Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15′ (2,3 Ghz Intel Core i7, 16GB DDR4).
  • Sistema Operativo: Mac OS Catalina 10.15.5
  • Entorno de desarrollo: JDK 11, IntelliJ, Docker, Postman

 

Introducción

La estrategia que se seguirá en los artículos será enseñar la teoría para tener una idea de cuál sería el funcionamiento, e ir añadiendo, sobre un proyecto desde cero, los conceptos aprendidos mediante ejemplos que las cumplan.

Este artículo está dividido en 5 partes:

 

¿Qué es Mountebank?

Mountebank es una herramienta de virtualización de servicios que proporciona una API REST para crear y configurar servicios virtuales, que se denominan impostores. En lugar de configurar el servicio objeto de prueba para que apunte a las URLs de servicios reales, se configura para que apunte a los impostores que creamos a través de la API de Mountebank.

Dentro del ecosistema de herramientas de virtualización de servicios WireMock es probablemente la alternativa más popular a Mountebank, pero también existen otras herramientas como Hoverfly, Moco y stubby4j entre otras.

 

Cómo funciona Mountebank

Mountebank se centra en los impostores. Un impostor define cómo debe funcionar un servicio de mock. Cada impostor representa un socket que actúa como el servicio virtual. El primer trabajo del impostor es simplificar una petición específica de protocolo en una estructura JSON para que pueda comparar la petición con un conjunto de predicados.

Cada impostor se configura con una lista de stubs (comprobantes). Un stub devuelve una respuesta establecida basada en la solicitud, un stub no es más que una colección de una o más respuestas y, opcionalmente, una lista de predicados.

Para entender qué son y cómo funcionan los impostores es necesario conocer primero cómo se comunican los servicios, para ello vamos a ver un ejemplo de una petición HTTP y su respuesta y a continuación veremos cómo Mountebank la “traduce” a JSON para trabajar con los impostores más fácilmente.

Aquí podemos ver un ejemplo sencillo de petición/solicitud:

Donde la primera línea de cualquier petición HTTP contiene tres componentes: el método, la ruta, y la versión del protocolo.

En este ejemplo, se trata de una petición de tipo GET, que denota que se quiere recuperar información en lugar de intentar cambiar el estado de algún recurso del servidor. Su ruta es /courses, y está utilizando la versión 1.1 del protocolo HTTP.

La segunda línea inicia los encabezados (headers), que son un conjunto de nuevos pares de valores clave separados por líneas.

En el ejemplo, vemos que tenemos el encabezado Host que se combina con la ruta y el protocolo para dar la URL completa como se vería en un navegador: http://api.autentia.com/courses. El encabezado Accept le indica al servidor que está esperando que devuelva JSON.

En cuanto a las respuestas, es bastante similar.

La primera línea de la respuesta contiene los metadatos, donde el código de estado/respuesta es el más importante. Las cabeceras (headers) siguen una vez más a los metadatos y en último lugar el cuerpo (body) que siempre está separado de los encabezados por una línea vacía.

Mountebank traduce los campos del protocolo de aplicación HTTP a JSON tanto para las solicitudes como para las respuestas, en el siguiente ejemplo podemos ver como se encuentran claramente distinguidas propiedades que hemos visto de la llamada HTTP como pueden ser el método, el path, el body, etc, dentro de la estructura JSON del impostor.

¿Y entonces, cómo es el proceso que realiza exactamente Mountebank cuando recibe una solicitud?

Mountebank pasa la solicitud a cada stub en orden de lista y escoge el primero que coincida con todos los predicados. Si la solicitud no coincide con ninguno de los stubs definidos, mountebank devuelve una respuesta predeterminada. De lo contrario, devuelve la primera respuesta para ese stub. Siguiendo con nuestro ejemplo, podemos ver que frente a un impostor como el siguiente Mountebank nos devolverá la primera respuesta del segundo stub.

{
...
    "stubs": [{
            "predicates": [{
                "deepEquals": {
                    "method": "POST",
                    "path": "/courses",
                    "query": {
                        "page": 1,
                        "limit": 50
                    },
                    "body": {
                        "key": "abc123"
                    }
                }
            }],
            "responses": [{
                "is": {
                    "body": {
                        "courses": [{
                                "id": "c9c87d8f41a6",
                                "name": "Ejb3 Timer Service: Scheduling",
                                "level": "INTERMEDIATE"
                            },
                            {
                                "id": "13694821e30c",
                                "name": "Envío de correo electrónico con el soporte de Jboss Seam",
                                "level": "BASIC"
                            },
                            {
                                "id": "ab495fdeb18a",
                                "name": "Registro dinámico de beans en el contexto de Spring",
                                "level": "ADVANCED"
                            }
                        ]
                    }
                }
            }]
        },
        {
            "predicates": [{
                "deepEquals": {
                    "method": "POST",
                    "path": "/courses",
                    "query": {
                        "page": 2,
                        "limit": 50
                    },
                    "body": {
                        "key": "abc123"
                    }
                }
            }],
            "responses": [{
                "is": {
                    "body": {
                        "courses": [{
                                "id": "621dab355d67",
                                "name": "Integración de Spring con el envío de emails",
                                "level": "INTERMEDIATE"
                            },
                            {
                                "id": "0a38296474c2",
                                "name": "Spring Security: haciendo uso de un servidor LDAP embebido",
                                "level": "ADVANCED"
                            },
                            {
                                "id": "877454fb00de",
                                "name": "Primeros pasos con github: subir un proyecto al repositorio.",
                                "level": "BASIC"
                            }
                        ]
                    }
                }
            }]
        },
        {
            "responses": [{
                "courses": []
            }]
        }
    ]
}

 

«Dockerizando» Mountebank

Desde mi punto de vista una de las formas más sencillas de trabajar con Mountebank dentro de nuestros servicios Java es la que os voy a comentar ya que no sería necesario realizar instalaciones manuales mediante node y arrancar manualmente nuestro servidor de mocks de Mountebank.

Hasta la versión 2.2.0, Mountebank no disponía de una imagen oficial docker por lo que lo más sencillo era crear nuestra propia imagen a partir del siguiente Dockerfile y subirlo a nuestro repositorio de imágenes:

FROM node:10.11.0-alpine
ENV MB_VERSION=1.16.0
RUN npm config set unsafe-perm true && npm install -g mountebank@${MB_VERSION} --production && mkdir -p /mountebank/servers
CMD [ -s /mountebank/servers/service.ejs ] && mb --configfile /mountebank/servers/service.ejs --loglevel debug --allowInjection
EXPOSE 2525 80

Con el siguiente comando construimos nuestra imagen de Mountebank customizada:

docker build --tag mountebank:0.0.1 .

Con el siguiente comando “taggeamos” nuestra imagen antes de subirla al repositorio:

docker tag mountebank:0.0.1 nexus.<url-repositorio>/mountebank:latest

Y por último, la subimos a nuestro repositorio:

docker push nexus.<url-repositorio>/mountebank:latest

Una vez que tenemos la imagen alojada en nuestro repositorio, vamos a crear dentro de nuestro servicio un docker-compose que configuraremos con dicha imagen y que al arrancar levante nuestro servidor de Mountebank.

version: '3.7'
networks:
 autentia:
   name: autentia
   driver: bridge
services:
 autentia-it-mountebank:
   image: mountebank_image:latest
   ports:
     - "80:80"
     - "2525:2525"
   volumes:
     - "../mountebank:/mountebank/servers"
   networks:
     - autentia

Como he comentado anteriormente desde la versión 2.2.0 es posible crear nuestro docker-compose configurando la imagen oficial de Mountebank, dando como resultado el siguiente archivo:

version: '3.7'
networks:
 autentia:
   name: autentia
   driver: bridge
services:
 autentia-it-mountebank:
   image: bbyars/mountebank:latest
   command: mb start --configfile /mountebank/servers/service.ejs --loglevel debug --allowInjection
   ports:
     - "80:80"
     - "2525:2525"
   volumes:
     - "../mountebank:/mountebank/servers"
   networks:
     - autentia

 

Probando nuestra configuración

Como ya hemos comentado anteriormente podemos crear nuestros impostores usando la API RESTful de Mountebank o a través de configuración si nos fijamos en nuestro docker-compose previo podemos observar que hemos configurado nuestro servidor de Mountebank para que lea del directorio mountebank de nuestro proyecto, por lo que bastaría con configurar en dicho archivo nuestros impostores antes de levantar el servidor.

Vamos a verlo con un ejemplo, creamos una carpeta dentro de ‘src/test/resources’ que se llame mountebank, dentro de ella vamos a crear el archivo ‘service.ejs’ con el siguiente contenido:

{
 "port": 80,
 "protocol": "http",
 "name": "origin",
 "defaultResponse": {
   "statusCode": 400
 },
 "stubs": [
   {
     "predicates": [
       {
         "deepEquals": {
           "method": "GET",
           "path": "/courses",
           "query": {
             "page": 1,
             "limit": 50
           }
         }
       }
     ],
     "responses": [
       {
         "is": {
           "statusCode" : 200,
           "headers":{
               "Content-Type":"application/json"
           },
           "body": [{
             "id": "fb68d450-3038-4bd5-9854-9cfba4dc5fb5",
             "name": "Ejb3 Timer Service: Scheduling",
             "level": "INTERMEDIATE"
           },
           {
             "id": "b2aa00b2-5fff-43c0-a9ca-172169b4fd5d",
             "name": "Envío de correo electrónico con el soporte de Jboss Seam",
             "level": "BASIC"
           },
           {
             "id": "13b88aee-1412-4290-b9a0-d6ea4ba5ca0f",
             "name": "Registro dinámico de beans en el contexto de Spring",
             "level": "ADVANCED"
           }]
         }
       }
     ]
   },
   {
     "predicates": [
       {
         "deepEquals": {
           "method": "GET",
           "path": "/courses",
           "query": {
             "page": 2,
             "limit": 50
           }
         }
       }
     ],
     "responses": [
       {
         "is": {
           "statusCode" : 200,
           "headers":{
               "Content-Type":"application/json"
           },
           "body": [{
             "id": "e646a1dc-61a8-4324-8fd4-58f84328be5d",
             "name": "Integración de Spring con el envío de emails",
             "level": "INTERMEDIATE"
           },
           {
             "id": "f8d918da-7eb7-4423-8662-b8bf1ffdb2d5",
             "name": "Spring Security: haciendo uso de un servidor LDAP embebido",
             "level": "ADVANCED"
           },
           {
             "id": "d355ef6e-1f12-4731-9ae7-64a863aca822",
             "name": "Primeros pasos con github: subir un proyecto al repositorio.",
             "level": "BASIC"
           }]
         }
       }
     ]
   },
   {
     "responses": [
       {
         "is": {
           "statusCode" : 200,
           "headers":{
               "Content-Type":"application/json"
           },
           "body": []
         }
       }
     ]
   }
 ]
}

Configurado nuestro impostor, vamos a levantar nuestro contenedor con la imagen del servidor Mountebank con el siguiente comando:

docker-compose up

Para probar nuestro impostor bastaría con ejecutar el siguiente curl, donde estamos simulando la llamada a nuestro servicio real de cursos pero invocando a nuestro servicio virtualizado de Mountebank en el puerto 80 definido anteriormente.

curl --location --request GET 'http://localhost:80/courses?page=2&limit=50'

Como podéis observar obtenemos la respuesta que configuramos en nuestro impostor:

{
    "courses": [
        {
            "id": "621dab355d67",
            "name": "Integración de Spring con el envío de emails",
            "level": "INTERMEDIATE"
        },
        {
            "id": "0a38296474c2",
            "name": "Spring Security: haciendo uso de un servidor LDAP embebido",
            "level": "ADVANCED"
        },
        {
            "id": "877454fb00de",
            "name": "Primeros pasos con github: subir un proyecto al repositorio.",
            "level": "BASIC"
        }
    ]
}

A partir de este momento cualquier llamada que hagamos al servicio de cursos desde nuestro servicio va a ser interceptada por Mountebank, el cual va a devolver la respuesta que hayamos configurado, decimos que “entrenamos” a Mountebank para que simule el servicio real.

 

Conclusiones

En este artículo hemos repasado de forma muy práctica cómo configurar nuestro servidor de Mountebank que puede ayudarnos en nuestro día a día con nuestros tests de microservicios.

Puedes descargar el proyecto completo aquí.

 

Referencias

http://www.mbtest.org/

https://github.com/bbyars/mountebank

https://github.com/bbyars/mountebank-in-action

https://hub.docker.com/r/bbyars/mountebank

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad