Te muestro como mockear APIs y servicios con Wiremock

Publicado el 16-07-2024 |

Autor: Pablo Caamaño

Es moneda corriente que en un proyecto de desarrollo se requiera consumir APIs de terceros, las cuales están definidas y documentadas, pero que no se encuentren desplegadas ni siquiera en un ambiente de desarrollo. Este escenario generalmente provoca muchos dolores de cabeza, ya que implica iniciar el desarrollo sin desarrollar las conexiones a estos desarrollos. Para casos como estos, contar con una herramienta que permita simular estas APIs de terceros aporta un valor muy grande, ya que de este modo se puede realizar un desarrollo integrando las conexiones y mapeos de datos.

La herramienta en cuestión se llama Wiremock, la cual básicamente permite imitar el comportamiento de una APIs (“mockear”) a modo de caja negra. Es decir, permite definir una cierta salida ante una determinada entrada de datos (request http) sin desarrollar el proceso intermedio (concepto de caja negra). Básicamente al detectar una request en alguno de los endpoints, con parámetros, headers y body específicos permite devolver diferentes respuestas según se lo defina. Es decir, permite generar “happy paths” y casos de errores ¿Suena útil no?

Tener en cuenta que para el desarrollo de este artículo y tutorial se van a tocar conceptos básicos sobre solicitudes Http y REST. Pero no te preocupes, si necesitas un poco de información previa, te invito a leer este artículo antes de avanzar.

¿Qué es REST y para qué sirve una API REST?

Configurar Wiremock

Para este articulo se va a utilizar wiremock de forma standalone, es decir un archivo jar ejecutable y portable en un proyecto que contendrá todos los mocks. No obstante, sepan que existe la posibilidad de instalarlo como una librería o dependencia dentro de cada proyecto que lo requiera. Sin embargo, considero que se puede aprovechar más como un servicio separado, de forma que pueda ser consumido por múltiples proyectos.

Entorno de desarrollo y ejecución

Para poder ejecutar Wiremock de forma local, basta con tener instalado el JDK de Java, se recomienda que al menos sea la versión 8. Si bien no es imprescindible, es recomendable para trabajar de forma más cómoda contar con un editor de texto plano como Visual Studio Code, Atom, Sublime Text, etc. También facilitará las tareas contar con un cliente http para realizar las request de forma sencilla, recomiendo Postman, aunque Insomnia o incluso curl desde una consola puede servir si te llevas bien con ellos. También sería muy útil contar con Git instalado para poder administrar y versionar los cambios que se vayan realizando.

Cabe destacar que no voy a profundizar en la explicación de como instalar y configurar el entorno de trabajo, ya que esto es algo muy personal de cada uno. Sin embargo, recomiendo chequear tener instalado correctamente el JDK ejecutando el comando java —version en la consola ya sea de Windows, Linux o Mac. Se debe visualizar la versión instalada, en caso de dar error es necesario revisar instalación y configuración de variables de entorno. Sin esto no se va a poder realizar los siguientes pasos.

Como siguiente paso, hay que descargar el archivo jar de wiremock, al cual acceden desde el siguiente enlace. Este archivo recomiendo alojarlo en un directorio donde se aloje el proyecto de wiremock. En este directorio raíz se recomienda inicializar el versionado con git, para que detecte los cambios de los subdirectorios.

La estructura requerida es muy simple, un subdirectorio mappings y otro __files dentro del proyecto. La estructura del proyecto quedará similar a la siguiente:

Con esta estructura armada, se puede abrir el proyecto en un editor de texto plano y agregar los archivos para armar los “mockeos”. Pero antes de eso recomiendo verificar que el proyecto se inicie correctamente. Para esto basta con abrir una consola y posicionarse en el directorio del proyecto ~/wiremock/ y ejecutar el comando: java -jar wiremock-standalone-3.4.1.jar --port 8086. Si en la consola aparece la palabra “WIREMOCK” en grande y con colores horribles (hubiera preferido rojo y blanco :P ) significa que el entorno está listo para usarse.

No quería obvia el detalle que en el comando utilizado se especificó el puerto 8086, es decir que el host a utilizar va a ser http://localhost:8086. Sin embargo, este número de puerto se puede cambiar según las necesidades del usuario. Además si se quiere apagar wiremock para hacer cambios, basta con precionar ctrl+c dentro de la terminal donde se realizó la ejecución del comando.

Como se dijo más arriba, es recomendable versionar el trabajo realizado en este proyecto para que sea fácil ver el historial de cambios y deshacer posibles errores. Para hacer esto basta con hacer un git init posicionados en el directorio raíz del proyecto wiremock.

¿Cómo funciona Wiremock?

Para explicar de forma resumida el funcionamiento de wiremock, basta con entender que cuenta con dos tipos de componentes. Los mappings que son los archivos donde se definen los endpoints, con su método, path, parámetros y headers. En estos archivos se definirá la salida, ya sea el estado Http y la respuesta asociada. Esto último se encuentran los files, que son los archivos donde se definirá el body de respuesta asociadas a uno o varios archivo mappings o a cada request.

Dicho de otra forma, en los mappings se mapea o definen los parámetros de entrada para responder con determinados datos de salida. Mientras que los files representan los archivos json, xml o del formato en que se desee a devolver la información de la respuesta. Para verlo de forma más concreta, a continuación se encuentra la estructura de un archivo “mapping” que se debe realizar para mockear una request.

json
{
    "request": {
        "method": "GET|POST|PUT|PATCH|DELETE|etc",
        "url": "/definir/path/aqui"
    },
    "response": {
        "status": "200|400|403|404|500|etc",
        "bodyFileName": "body-de-respuesta.json"
    }
}

En el ejemplo de arriba se describen los parámetros básicos para mockear un endpoint. Dentro del ítem "request" se encuentran los datos de la solicitud, como lo son el verbo o método y el path. Mientras que en ítem “response” se tiene que definir el estado http, así como opcionalmente un archivo con el body de la respuesta. Tener en cuenta que el bodyFileName se puede omitir en ese caso requerir una respuesta vacía .

Definición de Mocks

Para este apartado se va a utilizar un ejemplo concreto, con pasos definidor, con el fin de hacer una explicación entendible.

Mapeo de solicitud OK

Para iniciar, vamos a realizar una request con un "happy path". Es decir, se va a definir el mapeo de entradas y salidas correspondientes con un estado 200. Para iniciar dentro de ./mappings se va a generar un archivo JSON con un nombre descriptivo de la solicitud. Como se va a implementar un método GET se va a optar por nombrar get-data.json. También se va a generar el archivo con el body que responderá esta request, para esto, dentro de ./__filesse creará un archivo JSON que en este caso se llamará get-data-ok.json.

Ahora utilizando el editor de texto plano hay que abrir el archivo de mappings recién generado, es decir ./mappings/get-data.json. Basándonos en la estructura definida dos párrafos anteriores, reemplazaremos datos definiendo method GET y el path deseado dentro del campo "url". Por último, en la parte donde se encuentra “response” se definirá en “status” el valor 200 y en "bodyFileName" el path hacia el archivo de respuesta generado. Quedando de la siguiente forma:

json
{
    "request": {
        "method": "GET",
        "url": "/api/v1/data/1234"
    },
    "response": {
        "status": "200",
        "bodyFileName": "get-data-ok.json"
    }
}

Para este ejemplo voy a insertar una respuesta inventada al azar dentro del archivo ./__files/get-data-ok.json, el contenido a ingresar aquí lo dejo a tu criterio. Para este ejemplo, mi archivo de respuesta quedó así:

json
{
    "result": "OK",
    "data": [
        {
            "id": 9919,
            "description": "zaraza"
        },
        {
            "id": 9920,
            "description": "zaraza2"
        }
    ]
}

Solamente resta asegurarse de haber guardado todos los cambios y ejecutar el comando para levantar wiremock. Luego desde postman (o el software que desees utilizar) invocar la solicitud GET http://localhost:8086/api/v1/data y validar que se obtenga la respuesta definida.

Mapeo de solicitudes fallidas

En este ejemplo, se va a mapear un caso de error. Tener en cuenta que se tienen que mapear todos los casos de error que se deseen capturar en el entorno donde se esté utilizando. Para este ejemplo voy a simular un error 400. Este error representa que la solicitud está mal formada. Para dar un ejemplo concreto, si se borra parte del path en la request postman arriba utilizada esta fallara porque no "matchea" con la definición. Sin embargo, el error que se muestra es un error genérico de wiremock, es decir un 404 porque no encuentra una definición para esta solicitud.

Para manejar este error simplemente se debe generar agregar el mapeo en el archivo get-data.json del directorio mapping. Para esto se tiene que modificar ligeramente la estructura, es decir definiendo una lista que contenga diferentes mapeos para la misma request como se muestra a continuación.

json
{
    "mappings": [
        {
            "scenarioName": "Get Data OK",
            "request": {
                "method": "GET",
                "url": "/api/v1/data/1234"
            },
            "response": {
                "status": "200",
                "bodyFileName": "get-data-ok.json"
            }
        },
        {
            "scenarioName": "Get Data Bad Request",
            "request": {
                "method": "GET",
                "url": "/api/v1/data"
            },
            "response": {
                "status": "400",
                "bodyFileName": "get-data-400.json"
            }
        }
    ]
}

Como se puede ver en el archivo mapping, se generaron dos escenarios dentro de la lista mappings. El primero es el mapeo para responder correctamente y el segundo caso es la captura del error. También se puede ver que en el 400 se agregó un body para mostrar en la respuesta del 400. De esta forma se pueden agregar tantos mapeos dentro de una misma request, ya sea para casos 200 o diferentes errores como 404 o 500.

Si se levanta el proyecto y se vuelve a ejecutar la request mal formada, se podrá apreciar que se obtiene el 400 esperado y la respuesta definida. Mapear los casos de error, también es importante para el desarrollo, ya que el desarrollo que consuma este mock podrá tener un escenario lo más cercano a lo real.

Mapeo de otros verbos REST, Body y Headers

Hasta el momento se viene trabajando con request sencillas, utilizando el verbo GET. Sin embargo, con wiremock se pueden generar mapeos de todos los verbos existentes. Para dar un ejemplo un poco más complejo, vamos a generar un mock para una request POST en la cual se a definir un body y headers además del path. Para iniciar vamos a generar un nuevo archivo dentro de mappings que para el ejemplo llamaremos post-data.json.

json
{
    "mappings": [
        {
            "scenarioName": "Post Data OK",
            "request": {
                "method": "POST",
                "urlPattern": "/api/v1/data/(.*?)",
                "headers": {
                    "Authorization": {
                        "contains": "a1b2c3d4"
                    }
                },
                "bodyPatterns": [
                    {
                        "contains": "id"
                    },
                    {
                        "contains": "description"
                    }
                ]
            },
            "response": {
                "status": "201"
            }
        }
    ]
}

El mapping para este ejemplo es más complejo, pero si se analiza el funcionamiento de cada definición no lo es tanto. En primer lugar se configuró el método POST, luego se ve un item "headers" el cual exige que la request envíe una key Authorization y un value que tiene que coincidir con lo que se encuentra a la derecha del "contains". Más abajo se encuentra otro item nuevo llamado "bodyPatterns", el cual tiene una lista de "contains". Es decir, el body a enviarse en la request debe contener estos textos, sino no será considerada válida.

Otros detalles a tener en cuenta es que se reemplazó "url" por "urlPattern", y en el path definido se cambió el “/1234” por "/(.*?)" que representa un comodín. De este modo, se puede enviar cualquier número allí y lo tomará como valido. Así nos ahorramos definir un path por cada número que se quiera enviar. También se puede ver que en la respuesta se definió que responda un 201 y no se definió ningún body. Con estas definiciones, la solicitud funciona de la siguiente manera:

Con todo esto visto, ya se cuentan con suficientes herramientas para mockear las request que normalmente se utilizan. Si se quiere acceder al código original utilizado para estos ejemplos se encuentra publicado en GitHub.

Recursos adicionales

En este artículo se mostró esta herramienta de forma muy básica, priorizando mostrar como funciona, el valor que aporta y como armar algo básico para iniciar. Sin embargo, las posibilidades que ofrece Wiremock son muchas más, por lo cual dejo el link a la documentación oficial para que cada uno profundice lo que considere necesario en base a sus requerimientos.

En caso de tener problemas con el jar utilizado en este artículo, también les dejo el acceso a la versión 3.3.1 además de la versión 3.4.1 utilizada.

También les recuerdo que se puede adaptar el comando de ejecución a la versión y puerto requerido java -jar wiremock-standalone-{VERSION}.jar --port {PUERTO} . Y sin más que decir, te invito a implementar Wiremock en tus proyectos y que me cuentes si conocías esta herramienta o si conoces otra opción mejor. Lo mismo, cualquier duda o aclaración, te invito a que lo dejes comentarios, o que me escribas por correo electrónico, así como desde Telegram.
Sin más que agregar, nos leemos la próxima, saludos!

Contribuir con el Blog

Dejame tú comentario:

Comentarios anteriores

No se registraron comentarios