Desarrollar servicios web con WireMock

Hasta el año 2015, momento del nacimiento de Sngular, el nombre de "s|Software" era "Medianet Software"


¿Qué es un mock?

En el caso que nos ocupa entendemos como mock un objeto de respuesta que hemos generado manualmente para suplantar a una ejecución de un servicio web real.

¿Por qué los Mocks?

El uso de mocks (y más concretamente en el ambito de servicios web) ayudará a que una vez definida la interfaz de comunicación entre el servidor y sus clientes, cada uno de ellos pueda continuar con su desarrollo de forma independiente sin generar dependencias entre ellos durante la etapa de desarrollo de la funcionalidad de ambas capas.Por ejemplo, si estamos creando una plataforma de servicios REST, una vez definamos las entradas y salidas de nuestros servicios, permitimos que los clientes que vayan a integrarse en nuestra plataforma puedan ir desarrollando su funcionalidad y pruebas. En base a peticiones y respuestas preparadas para diferentes ejecuciones y resultados (cuanta más variedad, mejor) nos permite desarrollar con independencia de los servicios. También, en caso de tener una integración con un sistema externo al cual no queremos invocar invocar en tiempo de construcción, con los mocks para nuestros test podemos simular las llamadas y comprobar el comportamiento deseado según un resultado concreto.

¿Qué es WireMock?

WireMock es una librería orientada a testing y mocks de servicios web. Esto nos permite crear respuestas mock de nuestra propia API, tener respuestas mock de servicios externos, dar soporte a distintas plataformas con un repositorio de respuestas comunes, etc...

Entre algunas curiosidades que nos aporta WireMock es el poder incluir cabeceras en las respuestas que devuelve o tener distintos tipos de respuestas basadas en url, parámetros de request y path.

La librería pone a nuestra disposición dos funcionalidades base:

  • servicio standalone que permite tener un servidor web con mocks de servicios web
  • librería de testing para ser usada en los test unitarios y poder tener respuestas mock de servicios externos


Standalone

La versión standalone, nos permite tener en un corto período de tiempo un servidor web que responde a distintas peticiones web según su url y parámetros.

Lo primero que debemos hacer es descargar el .jar" de wiremock que luego tendremos que ejecutar.
En esta URL podemos acceder a la última versión (actualmente la 1.46) y nos descargarnos el fichero nombrado como wiremock-x.xx-standalone.jar"

Para iniciar el servidor mock, basta con ejecutar en una consola de windows (terminal en unix/mac) el comando:

[code lang="vb"]
java -jar wiremock-1.46-standalone.jar
[/code]

Esto iniciará un servidor en el puerto 8080 de nuestro localhost (http://localhost:8080). Al ejecutar dicho comando, se nos crea automáticamente dos directorios:

  • __files: aquí tendremos nuestros ficheros json con las respuestas que deseemos devolver
  • mappings: aquí tendremos nuestros ficheros json donde mapearemos url a distintas respuestas

Si entrásemos directamente en la url http://localhost:8080 lo que nos encontraríamos sería:



Esto significa que tenemos el servidor iniciado correctamente pero no tenemos nada mapeado en esa dirección.

Algunos parámetros útiles que podemos pasar al comando para iniciar el servidor serían:

  • port: para indicar el puerto en el que queremos levantar el servidor
  • https-port: para las peticiones que queremos realizar por https
  • https-keystore: para securizar el servidor con un certificado propio
  • root-dir: para indicar la ruta bajo la que tendremos los directorios __files y mappings
  • record-mappings: permite guardarnos peticiones que recibimos

Desde el principio tendremos disponible la url http://localhost:8080/__admin donde podremos ver las url que ya tenemos mapeadas.

El siguiente paso es añadir nuestros propios mocks. Vamos a verlo de dos formas distintas:

-Ficheros json

Lo primero que vamos a hacer es crear nuestro fichero json de mapeo

[code lang="vb"] {
"request": {
"method": "GET",
"url": "/api/example"
},
"response": {
"status": 200,
"headers": {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE",
"Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept",
"Content-Type": "application/json"
},
"bodyFileName": "/example.json"
}
}
[/code]

Este fichero lo guardamos como example_get.json en la carpeta mappings. Estamos definiendo en él:

  • request: petición que esperamos recibir
    • method: método que vamos a escuchar
    • url: url que estamos mapeando para el método antes indicado
  • response: respuesta que devolveremos para la descripción de la petición antes definida
    • status: código http que vamos a devolver
    • headers: en caso de desearlo, podemos indicar cualquier cabecera que queramos devolver
  • bodyFileName: para las peticiones en las que queremos devolver algún dato, podemos hacer indicando el fichero json que contiene dichos datos, este fichero estará almacenado en el directorio __files


Ahora tendremos que crear el fichero con el contenido que queremos devolver:

[code lang="vb"] {
"examples":[
{"id":1, "title":"Title1"},
{"id":2, "title":"Title2"},
{"id":3, "title":"Title3"}
] }
[/code]

El contenido es un simple fichero json con una estructura de datos. Este fichero lo guardamos como example.json en la carpeta __files.

Si reiniciamos el servidor podemos ver que ahora la url http://localhost:8080__admin tiene algo como esto:



Como vemos tenemos mapeada una url /api/example. Si entramos en la url http://localhost:8080/api/example obtendremos la respuesta que habíamos creado para dicho mapeo:



Cada vez que creemos un nuevo fichero de mapeo (añadamos o modiquemos ficheros en mappings) tendremos que reiniciar el servidor, sin embargo, somos libres de modificar en cualquier momento los ficheros de respuesta que tengamos en __files y el cambio será inmediato.

Otro ejemplo con una pequeña variación en su definición sería:

    [code lang="vb"] {
    "request": {
    "method": "GET",
    "urlPattern": "/api/example/[0-9]+"
    },
    "response": {
    "status": 200,
    "headers": {
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE",
    "Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept",
    "Content-Type": "application/json"
    },
    "bodyFileName": "/example_one.json"
    }
    }
    [/code]

   [code lang="vb"] {
    "request": {
    "method": "GET",
    "urlPattern": "/api/example/-[0-9]+"
    },
    "response": {
    "status": 200,
    "headers": {
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE",
    "Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept",
    "Content-Type": "application/json"
    },
    "bodyFileName": "/example_one_error.json"
    }
    }
    [/code]

En esos ejemplos de mapeo se ha usado urlPattern" en lugar de url" lo que nos permite poner algo más de variabilidad en la url que pedimos:


[code lang="vb"]
{"id":1, "title":"Title1"}
[/code]

ó

[code lang="vb"] {
"error":{
"code":404,
"text":"bad id"
}
}
[/code]

-Vía CURL

Otra de las opciones para añadir mapeos sin necesidad de reiniciar el servidor es mediante el comando curl. Los mapeos añadidos de esta forma serán visibles mientras no se reinicie el servidor.

Un ejemplo sería:

[code lang="vb"]
curl -X POST data { "request": { "url": "/api/examplecurl", "method": "GET" }, "response": { "status": 200, "body": "{"examples ":[{"id":1, "title":"Title1"},{"id":2, "title":"Title2"},{"id":3, "title":"Title3"}]}" }} http://localhost:8080/__admin/mappings/new
[/code]

Una vez ejecutado el comando, sí entrasemos en http://localhost:8080/__admin veríamos que está creado nuestro mapeo y al ejecutar la url http://locahost:8080/api/examplecurl obtenemos la respuesta:



Junit

Como librería, WireMock, nos da soporte para nuestros test unitarios, permitiendo crear mocks de servicios externos dentro de nuestro mock.

Partiendo del caso que tenemos un proyecto Java gestionado por Maven y con Junit4.10 o superior vamos a describir los pasos para tener nustro test con mock de un servicio externo funcionando.
Empezamos por añadir la dependencia de WireMock a nuestro pom.xml (ahora mismo la 1.46):

[code lang="xml"]
com.github.tomakehurst
wiremock
1.46

[/code]

En la documentación oficial indican que en caso de conflicto con Guava, Jetty o Apache HTTP podemos añadir la línea:

[code lang="xml"] standalone [/code]

En nuestra clase de test tendremos que preparar para que levante el servidor de wiremock para ejecutar los test. Podemos hacer lo dos formas:

Levantando y parando el servidor para cada uno de los test:

[code lang="java"]
@Rule
public WireMockRule wireMockRule = new WireMockRule(8089);
[/code]

El parámetro del puerto es opcional (y se iniciará en el 8080)

Manteniendo el contexto entre cada uno de nuestros test:

[code lang="java"] @ClassRule
public static WireMockClassRule wireMockRule = new WireMockClassRule(8089);
@Rule
public WireMockClassRule instanceRule = wireMockRule;
[/code]

Ahora nos queda mockear el servicio que queramos en cada uno de nuestros test:

[code lang="java"] @RunWith(JUnit4.class)
public class HttpPostCallTest {
@Rule
public WireMockClassRule wireMockRule = new WireMockClassRule(9080);
@Test
public void testPost(){
String responseText = "Some content";
wireMockRule.stubFor(get(urlEqualTo(/helllo/hello.json"))
.willReturn(aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody("{response:{"response":""+responseText+""}}")));
HttpPostCall httpPostCall = new HttpPostCall();
String result = httpPostCall.post();
Assert.assertNotNull(result);
}
}
[/code]

Podemos ver como creamos la instancia de WireMockClassRule, con el puerto que queramos usar, que luego usaremos para crear el stub.
En la url que queremos mockear se debe poner únicamente el path relativo es decir sin el host y puerto (por ejemplo: localhost:8080).
En el mismo stub, además podemos definir que tipo de estado http queremos devolver, cabeceras y contenido.

Pues obtener un ejemplo completo de este código en https://github.com/amcereijo/wiremocktest

Aplicación Web

Si lo que queremos es desplegar nuestro wiremock como una aplicación web en un contenedor tipo Tomcat, Jetty… Lo más fácil es crearnos un simple proyecto Maven y con una pequeña configuración, estará listo.

Lo primero es crear nuestro proyecto Maven clásico, desde el IDE Eclipse es tan sencillo como:
Archivo ? Nuevo ? Otros ? Maven Project
Elegimos la localización y como modelo base elegimos:
Group id: org.apache.maven.archetypes
Artifact Id: mave-archetype-webapp
Damos el Group id y Artifact id para nuestro proyecto y finalizamos.

Ahora copiamos nuestros directorios __files y mappings a una carpeta llamada wiremock dentro de /WEB-INF.

Lo siguiente es configurar nuestro web.xml para que sepa manejar las peticiones que hagamos. Algunas cosas que debemos poner son:

Directorios para ficheros

[code lang="xml"]

WireMockFileSourceRoot
/WEB-INF/wiremock

[/code]

Servlet para peticiones

[code lang="xml"] a de WireMock >

wiremock-mock-service-handler-servlet
com.github.tomakehurst.wiremock.servlet.HandlerDispatchingServlet

RequestHandlerClass
com.github.tomakehurst.wiremock.http.StubRequestHandler




wiremock-mock-service-handler-servlet
/*

[/code]

Servlet para peticiones admin

[code lang="xml"]


wiremock-admin-handler-servlet
com.github.tomakehurst.wiremock.servlet.HandlerDispatchingServlet

RequestHandlerClass
com.github.tomakehurst.wiremock.http.AdminRequestHandler




wiremock-admin-handler-servlet
/__admin/*

[/code]

El siguiente paso es configurar nuestro pom.xml para que obtenga las dependencias de WireMock y sea capaz de generar un war que podamos desplegar.

Dependencias

[code lang="xml"]

junit
junit
3.8.1
test


javax.servlet
servlet-api
2.4


log4j
log4j
1.2.16


com.github.tomakehurst
wiremock
1.45


[/code]

Proceso de construcción

[code lang="xml"]
org.apache.maven.plugins
maven-war-plugin
2.4

web/**


target/${project.build.finalName}/web/src/main/webapp/
/
true




[/code]

Como añadido hemos incluido el plugin de Jetty para poder arrancar nuestra aplicación con simple comando maven:

[code lang="vb"]
mvn jetty:run
[/code]

Sí lo hacemos veremos que tenemos desplegado en http://localhost:9099/__admin los mapeos que hayamos hecho.

Pues obtener un ejemplo completo de este código en https://github.com/amcereijo/wiremockweb

Esto es sólo una pequeña muestra de cosas que podemos hacer con WireMock, en su web puede obtenerse más información.

Últimos posts