General Solutions

In shock from the power of WireMock

In a software project which is Java based and build upon the Spring framework, writing tests is a must. For stubbing within your Spring Boot application Wiremock is integrated in the Spring Cloud project and can be used for your tests.

However in some situations we’re keep copying and pasting stubs which will end up in redundant code. And a underestimated power of WireMock is it’s standalone server feature. The standalone server is based on the OpenApi Specification. The version of the OpenApi Specification will depend on the version which you’ll end up using in your project.

In this case it had taken some time for research but eventually it ended up in a few lines of code. The rest is filled in by your specific project context and needs.

Ill show how to implement and use the WireMock standalone server / service in a few steps.

  • The Spring Boot project setup
    • Spring Boot starter template
    • Add Spring Boot Cloud dependency
    • Add WireMock configuration Bean
  • Get mappings overview
  • Add stubs (mappings)
  • Add advanced stubs (mappings)
  • Get files overview
  • Add stub files (files)
  • Add situation dependent mappings (Scenario’s)
  • Extra: Kubernetes Helm Chart for persistent data

The Spring Boot project setup

We’ve used the Spring Boot Starter Template which is much like the Initializer project generated project from Pivotal. We’re using the Maven variant but I’ll include the gradle code as well. This way we have a recent Spring Boot version and the spring-boot-starter-web dependency already present. We’re adding the Spring Cloud dependency to make use of the WireMock libraries which are under the spring-cloud-contract-stub-runner dependency of Spring Cloud.

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-wiremock</artifactId>
</dependency>

As you can point out there is no version specified, in our project we’re using the Spring Cloud Release Train and we defined that in our parent-pom which in our case specifies the version. However if you can look up the dependency in the maven repositories or jgoogle you’ll find a specific version which will suit your needs.

Sidenote: At the time of writing we’ve just upgraded our release train which solved a nasty bug in the WireMock swagger-ui which wouldn’t load through our ingress url. The WireMock was hereby upgraded from version 2.27.x to 2.31.0.

To make sure the WireMock can be used on our local environment as on the Kubernetes cluster we’ll end up using Spring Profiles. This way by setting our profile to the “dev” profile the settings are slightly different next to the production variant. Which is mainly focused on where the mappings and files are stored on the disk.

Also I thank the code guru’s of Pivotal for creating the possibility of creating Configuration beans as we ended up making one for the WireMock server itself. To clarify the last part of the previous sentence: WireMock will be started in a seperate webserver and thus on a different port. However the Spring Boot instance is also running, we’ve kept it running on the default which is 8080.

@Configuration
@Profile("dev")
public class StubServerConfigurationDev {
@Bean
public Options wireMockOptions() throws IOException {

final WireMockConfiguration options = WireMockSpring.options();
options.port(8000);

return options;
}
}

The production version is slightly different:

@Configuration
@Profile("prod")
public class StubServerConfigurationProd {
    @Bean
    public Options wireMockOptions() throws IOException {

        final WireMockConfiguration options = WireMockSpring.options();
        options.port(8000);
        options.withRootDirectory("/");

        return options;
    }
}

Ofcourse you can changed the ports according to your needs and specifications.

Our Configuration beans ended up in two flavours: dev and prod. Which is mainly because we wanted our data to be persistent on the cluster but WireMock default behaviour is to store the items in it’s resources folder which is only available in runtime and cleared on reboot. Not the ideal situation when a rolling update occurs of the service. To make sure the folders are stored on a local volume which can be mounted or linked we used the WireMockOptions object and set the withRootDirectory method with the attribute “/” to set the root folder as our starting point.

This will let WireMock look for the stubs and files in the following folder paths:

  • /__files for stored files
  • /mappings for the stored mappings(stubs)

Because we build ourself a Docker image and use that for ourKubernetes cluster with some help of a Helm Chart we can create a Persistent Volume Claim (PVC) to make sure our data is stored as the service can be updated/restarted. You can read more about it below in the Kubernetes Helm Chart section.

Get mappings overview

To get an overview of the current mappings a GET request can be send with your REST client or web browser as no parameters are required here. It’s the main mappings url, e.g. http://localhost:8000/__admin/mappings, you’ll need to enter and you’ll get the first 10 by default.

This can also be done when you enter the swagger-ui of the WireMock which is located on the /__admin/swagger-ui endpoint. This can be reached on my configuration with the example url stated like: http://localhost:8000/__admin/swagger-ui .

Add stubs (mappings)

To add stubs you’ll need a REST client, I’m using postman but any respectful REST client can make this magic happen.

Our first need for a good stub service was based on a external dependency towards another REST endpoint from another team. We already discussed the response and got ourself an example response. The project required the communication through JSON which is perfectly suitable for WireMock. Therefore the most example are filled with JSON response messages.

There are multiple ways to add a mapping into WireMock:

  • Manually creating a json file stored in the mappings folder ( requires a restart of the WireMock instance)
  • Send a POST request to the WireMock instance to add a mapping
    • This will put in in Runtime use, when rebooted without saving the mapping is gone (explained lower this blogpost).

We’re using the RESTful approach because it’s directly usable and we’re familiar with the API RESTful concept.

How to send a POST request to the WireMock instance

The POST request is not performed on the root url of the WireMock instance but instead on the admin mappings url, e.g. http://localhost:8000/__admin/mappings. This will end up with a POST request with a application/json body with the actual mapping on the example url will end up adding a mapping in runtime of the WireMock instance.

To make it persistent on the serverside of WireMock you’ll need to perform a POST request on the save endpoint of the admin mappings url. Note that this will save all currently not persisted mappings and make them persistent. So if some mappings are not desirable to keep, delete them before saving.

The save endpoint has no actual content needed to send. The POST request itself is enough. The url is dependeing on your configuration default on your localhost with the settings of this blogpost it would be: http://localhost:8000/__admin/mappings/save.

Mappings body structure

The main example given on the WireMock website of a mapping to send as body with the POST request is an example which returns plain text. I’m speaking of the following code example:

{
  "request": {
    "method": "GET",
    "url": "/some/thing"
  },
  "response": {
    "status": 200,
    "body": "Hello world!",
    "headers": {
      "Content-Type": "text/plain"
    }
  }
}

A little breakdown on this:

  • Request is the request where the WireMock responds to, in this case:
    • Get Request (All HTTP methods can be used)
    • Url relative to the hostname: /some/thing. e.g. http://localhost:8000/some/thing
  • Response is the response which is given when te request items match.
    • status is the HTTP statuscode which in this case is OK (200)
    • The body key value consists of HTML or plain text which in this case is a Hello world plain text.
    • The headers can also be specified, in some cases a response needs to have specific headers, this is the place to define them. In this example only one header is defined for the response.

However this wasn’t suitable for our needs, we need a json to return instead of plain text. WireMock has builtin support for that, instead of the body tag there’s the jsonBody tag which can be provisioned with a pure JSON formatted message. For example:

{
  "request": {
    "method": "GET",
    "url": "/some/json"
  },
  "response": {
    "status": 200,
    "jsonBody": {"status":"Success","message":"Endpoint found"},
    "headers": {
      "Content-Type": "application/json"
    }
  }
}

The main difference is the jsonBody instead of the body tag and the pure JSON which is provided.

However this will suit our needs when a static content is needed based on a static request. When we need a little more flexibility there’s a number of ways we can provide some more dynamic approach.

Advanced Mappings

Advanced mappings are more dynamic or flexible mappings which can hold a little logic. The main way to add these request is still the POST request but the request body is more advanced.

Request Matching is such a way to provide output based on input. We’ve got an endpoint which needs to be filled through query parameters. This can be done by entering the full url as a static mapping but it can go the wrong way if there is anything in a parameter like an url.

To define these query parameters in a request matching mapping for WireMock the request part will be adjusted. An example would be:

{
  "request": {
    "urlPath": "/test/combination",
    "method": "GET",
    "headers": {
      "content-type": {
        "equalTo": "application/json"
      }
    },
    "queryParameters": {
      "id": {
        "equalTo": "1"
      }
    }
  },
  "response": {
    "status": 200,
    "body": "valid combination"
  }
}

As you can see the queryParameters are supported. In a url without the ablility to specify the exact query parameters this will look like: http://localhost:8000/test/combination?id=1 to test this one. You can give multiple values and multiple parameters which means you’ve got a little more flexibility here.

The flexibility is mainly on the request side in this case.

Prioritize mappings

Mappings can be prioritized but why? Because there can be some sort of catch-all principle in order of some endpoint strategies. An example context would be to have 3 endpoints, 1 and 2 have a specific response and the third is the catch-all. The responses together:

//Endpoint 1
{
  "priority": 1,
  "request": {
    "method": "GET",
    "url": "/service/endpoint1"
  },
  "response": {
    "status": 200,
    "body": "endpoint1"
  }
}
//Endpoint 2
{
  "priority": 1,
  "request": {
    "method": "GET",
    "url": "/service/endpoint2"
  },
  "response": {
    "status": 200,
    "body": "endpoint2"
  }
}
//Endpoint catch-all
{
  "priority": 2,
  "request": {
    "method": "GET",
    "urlPattern": "/service/.*"
  },
  "response": {
    "status": 200,
    "body": "endpoint not implemented"
  }
}

In the above mentioned priorities the WireMock will first check the priority 1 mappings and will sequentially go to the lower priority which has a wildcard in it (” /service/.* ” ). The wildcard .* will accept anything behind the /service/ endpoint to react upon and give the stated response message.

Get file overview

To get an overview of the current files a GET request can be send with your REST client or web browser as no parameters are required here. It’s the main files url, e.g. http://localhost:8000/__admin/files, you’ll need to enter and you’ll get the overview.

This can’t be done by the swagger-ui as it’s not provided in their documentation for some reason.

Adding files

Not perfectly documented to my honest opinion but most certainly possible, add files to the WireMock and retrieve them. This requires two steps:

  1. Adding the file to the __admin/files endpoint (PUT HTTP method)
  2. Adding a mapping which points to the uploaded file.

To add a file to WireMock you’ll need to perform a PUT request to the WireMock on the files url like http://localhost:8000/__admin/files/example.pdf and send the PDF file as binary. As you’ll probably will see in the example I need to give the filename in the url because the file is send as a binary (bytestream) and doesn’t contain any data about its name. Therefore you’ll need to enter the desired filename.

Sidenote: you can’t enter folders because WireMock isn’t able to create them, this will end up in a 404 not found. Good and bad example:

  • Good: http://localhost:8000/__admin/files/test.pdf
  • Bad: http://localhost:8000/__admin/files/documents/pdf/manual.pdf

When successfully performed the response of the WireMock is the contents as ascii characters, don’t know why but to manage expectations this is normal behaviour.

To add a mapping which points towards the document the following body can be used as an example:

{
  "request": {
    "method": "GET",
    "url": "/body-file"
  },
  "response": {
    "status": 200,
    "bodyFileName": "test.pdf"
  }
}

This will point the location of e.g. http://localhost:8000/body-file to the test.pdf we just uploaded. The url can be as long and complex as you like. The most important here is the bodyFileName tag which points to the filename you’ve provided with the upload.

Files are also reachable from te root url + filename like http://localhost:8000/test.pdf because they’re stored in a single folder (__files) which is a root folder of the webserver.

Don’t forget to save your mappings or they’ll be gone the next rolling update or release.

Add situation dependent mappings (Scenario’s)

These are like little program flows or as the official documentation mentioned it, state machines. It’s much like a flow which sets the state towards the following state. Don’t forget to set the state at the end to the starting state Scenario.START.

This will create the opportunity to create dynamic responses on a single url mapping which will setup the response according to the state it is in.

For example we’ll have an webshop and we’ll be buying an item. The starting state will be the overview of the shopping cart. The next state will be the checkout view and the last state will be the confirmation view. An abstract scenario but it puts it in perspective.

The example requests in sequential order:

{
  "mappings": [
    {
      "scenarioName": "Cart checkout",
      "requiredScenarioState": "Started",
      "request": {
        "method": "GET",
        "url": "/cart/checkout"
      },
      "response": {
        "status": 200,
        "body" : "<items><item>Jetson Nano</item></items>"
      }
    },
    {
      "scenarioName": "Cart checkout",
      "requiredScenarioState": "Started",
      "newScenarioState": "Checkout",
      "request": {
        "method": "POST",
        "url": "/cart/checkout",
        "bodyPatterns": [
          { "contains": "Checkout" }
        ]
      },
      "response": {
        "status": 201
      }
    },
    {
      "scenarioName": "Cart checkout",
      "requiredScenarioState": "Checkout",
      "request": {
        "method": "GET",
        "url": "/cart/checkout"
      },
      "response": {
        "status": 200,
        "body" : "<p>Jetson Nano successfully ordered</p>>"
      }
    }
  ]
}

To make sure your scenario works accordingly, the above json with the array of mappings is ment to be inserted per mapping. This means that every scenario state / response is inserted into the /__admin/mappings endpoint likewise as a static mapping.

Example POST scenario request to /__admin/mappings endpoint:

{
"scenarioName": "Cart checkout",
"requiredScenarioState": "Started",
"request": {
"method": "GET",
"url": "/cart/checkout"
},
"response": {
"status": 200,
"body" : "<items><item>Jetson Nano</item></items>"
}
}

Main difference is that the filtering by WireMock is done by the tag of scenarioName and can be requested on the /__admin/scenarios endpoint of the WireMock instance. No static mappings are shown here.

Don’t forget to save your mappings to make them persistent.

Extra: Kubernetes Helm Chart for persistent data

This is Just in one sentence there are multiple subjects which all are related to cloud implementation. These documents are added in the GitHub repository for yours to use in the helm folder of the project. I must mention it’s MIT license, so use it at your own expense.

GitHub link: //TODO: Will follow up after some preparation before making it public.

Sources

Leave a Reply

Your email address will not be published.