How To Create a Stub in 5 Minutes
Readers will learn how to create stubs in five minutes, which uses regression and load testing, debugging, and more, and how to configure the stubs flexibly.
Join the DZone community and get the full member experience.
Join For FreeIf you’re developing an application that sends REST requests to the API of another service, mocks and stubs will probably help with your testing. Mock objects are used in unit tests: a call to another service is simulated. A test stub is an HTTP server that completely mimics the behavior of the service with which we plan to interact during the production use of our application. But using this service is not suitable for testing purposes. The scope of stubs is wide: regression testing, load testing, debugging, and more.
Today, I will be as brief and, to the best of my ability, clearly talk about how to create stubs in a matter of minutes using the WireMock tool.
Options for Running the Stub
- As a standalone process
- In Docker
- Plain Java server
You need to choose one of the options according to your needs.
Running as a Standalone Process
I recommend using this particular stub launch option because it is simple and suitable in most situations. The undoubted advantage of this method is that there is no need to write code—the stub is completely configured using a JSON file. Although, if desired, you can also configure using the Java API.
To run the stub, we need the following:
- Download a ready-made JAR from the official site.
- Start a service that will listen on port 8080 by default (JRE required):
$ java -jar wiremock-jre8-standalone-2.33.2.jar
Some useful command line options:
--port
: the HTTP port number.--https-port
: the HTTPS port number.--verbose
: turn on verbose logging to stdout.--root-dir
: sets the root directory, under which mappings and files reside. This defaults to the current directory.--container-threads
: the number of threads created for incoming requests, which defaults to 10.--local-response-templating
: enable rendering of response definitions using Handlebars templates for specific stub mappings.
So the start of the service might look like this:
$ java -jar wiremock-jre8-standalone-2.33.2.jar –port 8090 --local-response-templating
Running in Docker
This option is not fundamentally different from the previous one. The stub is also configured using a JSON file.
- Download Docker image from the official site.
- Start a single WireMock container:
docker run -it --rm \
-p 8080:8080 \
--name wiremock \
wiremock/wiremock:2.35.0
Plain Java Server
In this option, we create our own application and configure the stub in it.
The simplest example:
public static void main(String[] args) {
WireMockServer server = new WireMockServer(
options()
.port(8080)
);
server.start();
/*
Configure the stub here
*/
}
To build the application, we need to specify the dependencies.
Maven (Java 8)
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8</artifactId>
<version>2.33.2</version>
</dependency>
Gradle (Java 8)
Implementation "com.github.tomakehurst:wiremock-jre8:2.33.2"
Configuring and Running Stubs
The Simplest Stub
It’s time to write a simple stub and test it out.
First, let’s configure the stub via a JSON file:
{
"request": {
"method": "GET",
"url": "/some/thing"
},
"response": {
"status": 200,
"body": "Hello world!",
"headers": {
"Content-Type": "text/plain"
}
}
}
We place the file in the /mappings
directory, where we consider the root directory to be the directory where your JAR file is located (standalone), or in the /mappings
directory in the Docker container.
Let’s check the functionality. We run the stub on the local machine and listen on port 8080. Send a GET
request to the following address: http://localhost:8080/some/thing.
$ curl http://localhost:8080/some/thing
Hello world!
As you can see, the request returns status 200 and the body «Hello world!»
If you are creating your own Java Server with a stub, then the code will be as follows:
public static void main(String[] args) {
WireMockServer server = new WireMockServer(
options()
.port(8080)
.containerThreads(20)
.jettyAcceptors(10)
.extensions(new ResponseTemplateTransformer(false))
);
server.start();
stubFor(get(urlEqualTo("/some/thing"))
.willReturn(aResponse()
.withHeader("Content-Type", "text/plain")
.withBody("Hello world!")));
}
Request Matching
Requests to the stub can be varied and differ from each other not only by the endpoint, but by the following attributes:
- HTTP method
- Query parameters
- Headers
- Cookies
- Request body
And for each case, we can write our own stub. That is, for example, for two identical requests that differ from each other only in headers, you can write two separate stubs, laying down your own logic depending on the headers used.
Here are some examples of the most used cases.
URL Matching
URLs can be matched either by equality or by regular expression.
{
"request": {
"urlPathPattern": "/endpoint[123]"
...
},
...
}
urlPathMatching("/endpoint[123]")
Headers Matching
Checking for a header «Content-Type: application/json
».
{
"request": {
...
"headers": {
"Content-Type": {
"equalTo": "application/json"
}
}
...
},
...
}
.withHeader("Content-Type", equalTo("application/json"))
Query Parameters
Let’s check that the request has an ID parameter and the value contains only numbers (we use a regular expression).
{
"request": {
...
"queryParameters" : {
"id" : {
"matches" : "\d"
}
}
...
},
...
}
.withQueryParam("id", matching("\d"))
Request Body
Check to see if the body matches the given JSON:
{
"request": {
...
"bodyPatterns" : [ {
"equalToJson" : { "total_results": 4 }
} ]
...
},
...
}
.withRequestBody(equalToJson("{ \"total_results\": 4 }"))
The same, but XML:
{
"request": {
...
"bodyPatterns" : [ {
"equalToXml" : "<thing>Hello</thing>"
} ]
...
},
...
}
.withRequestBody(equalToXml("<thing>Hello</thing>"))
Response Templating
When forming headers or the response body, it is possible to use data from the incoming request using the Handlebars template mechanism.
To enable this mechanism, if you configure the stub via JSON, you need to add the key transformers
(array) with the value “response-template
.”
{
"request": {
"urlPath": "/templated"
},
"response": {
"body": "{{request.path.[0]}}",
"transformers": ["response-template"]
}
}
If you are running the stub as a standalone process, you will need the --local-response-templating
command line switch to enable the templating engine.
For the stub as a Plain Java Server, an example of enabling the Handlebars templating mechanism has already been given above (see “The Simplest Stub” header).
WireMockServer server = new WireMockServer(
options()
.port(8080)
.containerThreads(20)
.jettyAcceptors(10)
.extensions(new ResponseTemplateTransformer(false))
);
The model of the request is supplied to the header and body templates. The following request attributes are available:
request.url
: URL path and queryrequest.path
: URL pathrequest.query.<key>
: first value of a query parameter, e.g.,request.query.search
.request.method
: request method, e.g.,POST
request.host
: hostname part of the URL, e.g.,my.example.com
.request.port
: port number e.g.8080
request.scheme
: protocol part of the URL, e.g., https.request.baseUrl
: URL up to the start of the path, e.g.,https://my.example.com:8080
.request.headers.<key>
: first value of a request header, e.g.,request.headers.X-Request-Id
.request.cookies.<key>
: first value of a request cookie, e.g.,request.cookies.JSESSIONID
.request.body
: request body text
Incoming request parameters or headers can be either single values or arrays. Therefore, templates provide convenient ways to work with data. For example, the incoming request URL /multi-query?things=1&things=2&things=3
.
{{request.query.things}} // Will return 1
{{request.query.things.0}} // Will return 1
{{request.query.things.first}} // Will return 1
{{request.query.things.1}} // Will return 2
{{request.query.things.[-1]}} // Will return 2
{{request.query.things.last}} // Will return 3
There are also a fairly large number of functions used in Handlebars templates for data manipulation. We use some of them in the example of a complex stub, which I will demonstrate below.
Complex Stub
It’s time to show a more complex example that demonstrates the power of stubs.
Let’s configure the stub via a JSON file:
{
"request": {
"method": "POST",
"urlPathPattern": "/templated[1234]",
"headers": {
"Content-Type": {
"equalTo": "application/json"
}
}
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"bodyFileName": "respBody.json",
"transformers": [
"response-template"
]
}
}
We see that we used a regular expression in the request URL. That is, we can access our stub using any of the four endpoints:
/templated1
/templated2
/templated3
/templated4
We also see from the configuration that only POST
requests with the "Content-Type": "application/json"
header will be processed. The exact same header will be added to the response. When generating a response, the Handlebars templating mechanism will be enabled. The response will be generated based on the respBody.json
file.
In the /__files
directory of the working directory of our stub, create a new respBody.json
file with the following content:
{
"resp": {
"RqUID": "{{jsonPath request.body '$.req.sys.RqUID'}}",
"RqTm": "{{jsonPath request.body '$.req.sys.RqTm'}}",
"CardNum": "{{jsonPath request.body '$.req.data.CardNum'}}",
"date": "{{now}}",
"randUID": "{{randomValue length=12 type='ALPHANUMERIC' uppercase=true}}",
"someValue": "value12345"
}
}
The file is basically standard JSON. Fields that will be transformed using the Handlebars templating mechanism are enclosed in double curly braces {{}}
.
Here are some features of the presented transformations:
- Using the
jsonPath
function, the values of thereq.sys.RqUID
,req.sys.RqTm
, andreq.data.CardNum
tags are extracted from the JSON structure (the body of the incoming request). - Using the
now
function, we got the current system date and time. - Using the
randomValue
function, we got a random value consisting of letters and numbers of a given length.
Let’s check how it works. We send a POST
request to the following address: http://localhost:8080/templated2.
Request Body:
{
"req": {
"sys": {
"RqUID": "1234567890f",
"RqTm": "2022-09-22T12:06:54"
},
"data": {
"CardNum": 3492374932472394
}
}
}
We will get the following answer:
{
"resp": {
"RqUID": "1234567890f",
"RqTm": "2022-09-22T12:06:54",
"CardNum": "3492374932472394",
"date": "2022-08-31T13:14:27Z",
"randUID": "PLOVSWNXQJR9",
"someValue": "value12345"
}
}
Conclusion
We are now able to create stubs in a matter of minutes and configure them very flexibly. Good luck!
Opinions expressed by DZone contributors are their own.
Comments