Creating your stateful sandbox with WireMock
Wiremock is a great tool to test your application against a real web service with predefined stubs. It allows us to write integration tests that do not stop at a rest client mock but do network communication, simulating different responses with limited effort.
But WireMock does not stop there: it also provides a standalone version that allows one to spin up a server with predefined stubs and thus is a fully functional web service.
For using WireMock as a sandbox, this article will give a brief overview of the following:
- Creating plain stubs
- Using dynamic templates for building responses
- Using scenarios to change responses based on previous requests
- Transporting state from a previous request to the next one
Why Create a Sandbox
When developing a web service with other external services, it is usually not desired to interact with these external services in the developing phase. Imagine doing branch deployments or having a staging environment and running manual and end-to-end tests. You might not want this to cause additional costs, have unwanted side effects — or take long.
One solution to this is a sandbox: an isolated environment for testing purposes. WireMock can be used to create one. I prepared a sample application encapsulated in a Spring Boot application and full examples for everything mentioned here. The standalone version would also work for many scenarios, but for one use case, you need a special extension :-).
GitHub – dirkbolte/medium-wiremock-sandbox
Plain Stubs
This use case is probably used the most often, especially in integration tests: Match a request and create a stub response. All requests will get the same answer.
{
"request": {
"method": "GET",
"url": "/plain",
"headers": {
"accept": {
"contains": "json"
}
}
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"jsonBody": {
"id": "oghrhc8uh0q0b5vmytxd7zvxm3ydkm5j",
"createdAt": "2023-06-21T12:35:48Z",
"updatedAt": "2023-06-21T12:35:48Z"
}
}
}
You can have multiple answers prepared and differentiate themes based on request parameters. Adding variations to it is cumbersome and might not be possible as you don’t want to leak sandbox information into your application.
Response Templating
You can add dynamic elements to your requests using response templating. For that, the ResponseTemplateTransformer extension has to be registered:
var server = new WireMockServer(options()
.port(8080)
.extensions(new ResponseTemplateTransformer(true));
Now, you can use handlebars templates in your responses to, e.g., calculate new values or use parts of the request part or request body:
{
"request": {
"method": "POST",
"urlPattern": "/template/jsonbody",
"headers": {
"accept": {
"contains": "json"
},
"content-type": {
"contains": "json"
}
}
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"jsonBody": {
"id": "{{randomValue length=32 type='ALPHANUMERIC' uppercase=false}}",
"firstKey": "{{jsonPath request.body '$.firstKey'}}",
"createdAt": "{{now}}",
"updatedAt": "{{now}}"
}
}
}
The response would look like this:
{
"id": "spkgecneggjwqguj5mfchc7n3lznzcmf",
"firstKey": "firstValue",
"createdAt": "2023-06-21T13:52:55Z",
"updatedAt": "2023-06-21T13:52:55Z"
}
Attention: In case you want to use a complete JSON object of the initial request, you need to:
- use body instead of jsonBody and thus have to write the whole JSON in one line
- use {{{jsonPath request.body ‘$.firstObject’}}} instead of “{{jsonPath request.body ‘$.firstObject’}}” (so, no quotation marks or triple curly braces)
Here’s an example:
"response": {
"body": "{"firstObject": {{{jsonPath request.body '$.firstObject'}}}}"
}
With this, you add some dynamics to your sandbox, but the behavior doesn’t change in between calls: the first GET to an API would behave the same way as the second GET.
Scenarios
WireMock scenarios introduce a state for requests. Scenarios have a name and a state (initially, Started ). For each mapping, you can define the scenario they belong to, the state they are associated with, and optionally a new state for the scenario once the request is processed.
Here’s an example:
- For scenario CRUD , the stubs are in state Started
- GET for state Started will return the HTTP status 404
- POST will return a JSON body and change the scenario state to Created
- GET for state Started will return the HTTP status 201 along with a hardcoded body
- DELETE will return the HTTP status 204 and change the scenario state to Started again
- GET for state Started will return the HTTP status 404 again
Here’s a shortened example:
{
"mappings": [
{
"scenarioName": "CRUD",
"requiredScenarioState": "Started",
"request": {
"method": "GET"
},
"response": {
"status": 404
}
},
{
"scenarioName": "CRUD",
"requiredScenarioState": "Started",
"newScenarioState": "Created",
"request": {
"method": "POST",
"urlPattern": "/scenario",
},
"response": {
"status": 201,
"jsonBody": {}
}
},
{
"scenarioName": "CRUD",
"requiredScenarioState": "Created",
"request": {
"method": "GET"
},
"response": {
"status": 200,
"jsonBody": {}
}
},
{
"scenarioName": "CRUD",
"requiredScenarioState": "Created",
"newScenarioState": "Started",
"request": {
"method": "DELETE",
},
"response": {
"status": 204
}
}
]
}
Scenarios introduce some states, but we cannot transfer anything from a previous request to the next. We can still apply response templating but can only use the data of the current request.
State
There’s no built-in way to transport state in between stubs. Stubs are relatively isolated. Luckily, WireMock allows it to be extended. A sample extension for transporting state in between requests is available on GitHub. The sample extension also supports templates in their definitions.
GitHub – dirkbolte/wiremock-extension-state
We register an extension for postServeActions to record a state as well as response templating helper to retrieve a previous state:
var stateRecordingAction = new StateRecordingAction();
var server = new WireMockServer(options()
.port(8080)
.extensions(
stateRecordingAction,
new ResponseTemplateTransformer(true, "state", new StateHelper(stateRecordingAction))
);
To record a state, define a postServeAction to store data in association with a context, retrieving data from the current request and response:
{
"response": {
"status": 201,
"headers": {
"Content-Type": "application/json"
},
"jsonBody": {
"id": "{{randomValue length=32 type='ALPHANUMERIC' uppercase=false}}",
"createdAt": "{{now}}",
"updatedAt": "{{now}}"
}
},
"postServeActions": [
{
"name": "recordState",
"parameters": {
"context": "{{jsonPath response.body '$.id'}}",
"state": {
"firstKey": "{{jsonPath request.body '$.firstKey'}}",
"createdAt": "{{jsonPath response.body '$.createdAt'}}",
"updatedAt": "{{jsonPath response.body '$.updatedAt'}}"
}
}
}
]
}
The data can now be retrieved in the GET request based on the context provided in the URL (so, e.g., the ID of the entity that was created previously):
{
"request": {
"method": "GET",
"urlPattern": "/[^/]+"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"jsonBody": {
"id": "{{request.pathSegments.[1]}}",
"firstKey": "{{state context=request.pathSegments.[0] property='firstKey'}}",
"createdAt": "{{state context=request.pathSegments.[0] property='createdAt'}}",
"updatedAt": "{{state context=request.pathSegments.[0] property='updatedAt'}}"
}
}
}
Conclusion
With WireMock, you can create a powerful sandbox for testing your application. You can already cover many scenarios with stubs, response templating, and scenarios. Its extension mechanism even allows it to cover more complex scenarios like transferring states depending on a context.
Sandbox creation requires minimal code and can be easily adapted to many third-party backend and their behavior.
Resources
More Than Just Stubs was originally published in Better Programming on Medium, where people are continuing the conversation by highlighting and responding to this story.