Creating a standalone stub server is important in order not to depend on an API that isn't yet available, isn't complete, or is expensive to access during the development phase. The front-end team can easily advance its functionality by simulating all the requests and responses that will be produced by the APIs later.
In this story, we'll explain how to build a standalone stub server for stubbing REST APIs using Spring Cloud Contract WireMock.
· Prerequisites · Overview ∘ What is WireMock? ∘ Spring Cloud Contract WireMock · Spring Boot Stub Server App Setup ∘ Project Setup ∘ Create Mock JSON Files ∘ Containerize application · Testing · Conclusion · References
Prerequisites
This is the list of all the prerequisites for following this story:
- Java 17
- Starter Boot 3.0.6
- Maven 3.6.3
- Optionally, Docker installed
- Optionally, Docker compose installed
- Postman or Insomnia
Overview
What is WireMock?
WireMock is a popular open-source tool for API mock testing. WireMock is a library for stubbing and mocking web services. It helps to create stable test and development environments, isolates 3rd parties, and simulates APIs that don't exist yet.
Spring Cloud Contract WireMock
The Spring Cloud Contract WireMock modules let you use WireMock in a Spring Boot application. It simplifies some aspects of configuration and eliminates some common issues that occur when running Spring Boot and WireMock together.
Spring Boot Stub Server App Setup
Project Setup
We will start by creating a simple Spring Boot project from start.spring.io, with the following dependencies: starter-web, actuator.

After that, we need to add the dependency related to the Spring Cloud Contract WireMock.
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-contract-wiremock -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-wiremock</artifactId>
<version>4.0.4</version>
</dependency>We need to add the AutoConfigureWireMock annotation to start a WireMock server in the context of the Spring application. This will bind the port, HTTPS port, and stub files and locations of the WireMock server when the Spring Boot application is started.

AutoConfigureWireMock annotation to the location of the parent directory (in other words, __files is a subdirectory). You can use a Spring resource notation to refer to file:… or classpath:… locations. Generic URLs are not supported. A list of values can be given.
In this case, I registered WireMock JSON stubs from the resources classpath mappings and __files directories.

To read the body content from a file, place the file under the
__filesdirectory. By default this is expected to be undersrc/test/resourceswhen running from the JUnit rule. When running standalone it will be under the current directory in which the server was started.
Create Mock JSON Files
To create the stub via the JSON API, the mock JSON document can either be posted to http://<host>:<port>/__admin/mappings or placed in a file with a .json extension under the mappings directory. Actually, WireMock always loads mappings from src/test/resources/mappings as well as the custom locations in the stubs attribute.
Let's start by creating a JSON mock for our book API.

- Auth Login

JSON mapping file:
{
"request": {
"method": "POST",
"url": "/v1/login",
"bodyPatterns": [
{ "matchesJsonPath": "$.login" },
{ "matchesJsonPath": "$.password" }
]
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "*"
},
"jsonBody": {
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJzeXN0ZW0iLCJ0aW1lem9uZSI6IlVUQyIsInJvbGVzIjpbIlJPTEVfTURfU0VUX0FETUlOIl0sImF2YXRhciI6bnVsbCwiYXV0aG9yaXRpZXMiOlsiTURfU0VUX0FETUlOIl0sImNsaWVudF9pZCI6Im9hdXRoX2NsaWVudF9pZCIsImF1ZCI6WyJyZXNvdXJjZV9pZCJdLCJzY29wZSI6WyJyb2xlX2FkbWluIiwicm9sZV91c2VyIl0sImRlZmF1bHRsb2NhbCI6ImZyIiwiaWQiOiI2MGFkMzMyZWY1MTg2N2I0NDhkZjE2MjIiLCJmdWxsbmFtZSI6InN5c3RlbSBhZG1pbiIsImV4cCI6MTYyMjc0MjA3Niwiam9iIjpudWxsLCJqdGkiOiI0NzFhMmEzZi0wY2UxLTRlNDMtOTNhNy0yZDhlODE3ZTk1ZTIiLCJlbWFpbCI6ImJvb3R0ZWNobm9sb2dpZXMuY2lAZ21haWwuY29tIiwiZGF0ZWZvcm1hdCI6IkREL01NL1lZWVkifQ.uyISDckvXXCk9Oqb8h-llOK6z05gmDzEESNZ45XBLLs",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJzeXN0ZW0iLCJ0aW1lem9uZSI6IlVUQyIsInJvbGVzIjpbIlJPTEVfTURfU0VUX0FETUlOIl0sImF2YXRhciI6bnVsbCwiYXV0aG9yaXRpZXMiOlsiTURfU0VUX0FETUlOIl0sImNsaWVudF9pZCI6Im9hdXRoX2NsaWVudF9pZCIsImF1ZCI6WyJyZXNvdXJjZV9pZCJdLCJzY29wZSI6WyJyb2xlX2FkbWluIiwicm9sZV91c2VyIl0sImF0aSI6IjQ3MWEyYTNmLTBjZTEtNGU0My05M2E3LTJkOGU4MTdlOTVlMiIsImRlZmF1bHRsb2NhbCI6ImZyIiwiaWQiOiI2MGFkMzMyZWY1MTg2N2I0NDhkZjE2MjIiLCJmdWxsbmFtZSI6InN5c3RlbSBhZG1pbiIsImV4cCI6MTYyMjc2MDA3Niwiam9iIjpudWxsLCJqdGkiOiIyYmVlODZkMy1jZGFiLTQ0NmYtYmIwNi1iYTcxNjgwYTU5NTciLCJlbWFpbCI6ImJvb3R0ZWNobm9sb2dpZXMuY2lAZ21haWwuY29tIiwiZGF0ZWZvcm1hdCI6IkREL01NL1lZWVkifQ.HZsmMgPaBllRfx8lDOX8OBupnzn9tJ7-6wK6PjrWk5s",
"expires_in": 3599,
"scope": "role_admin role_user",
"id": "60ad332ef51867b448df1622",
"email": "bootlabs@gmail.com",
"username": "admin",
"roles": [
"ROLE_MD_SET_ADMIN"
],
"jti": "471a2a3f-0ce1-4e43-93a7-2d8e817e95e2"
}
}
}The login response contains jwt user authentication access tokens.
- Get books

JSON mapping file:
{
"request": {
"method": "GET",
"url": "/v1/books"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "*"
},
"jsonBody": [
{
"id": 1,
"description": "netus et malesuada",
"isbn": "X4J 5H8",
"page": 62,
"price": 529.0,
"title": "arcu. Vestibulum ut",
"authorId": 9
},
{
"id": 2,
"description": "mollis non,",
"isbn": "M3Q 4G1",
"page": 15,
"price": 668.0,
"title": "Nullam ut",
"authorId": 2
},
{
"id": 3,
"description": "Maecenas mi felis, adipiscing fringilla, porttitor",
"isbn": "B5W 1Y8",
"page": 16,
"price": 708.0,
"title": "et ipsum cursus",
"authorId": 5
},
{
"id": 4,
"description": "eros turpis non enim. Mauris quis turpis",
"isbn": "Q1O 7Y6",
"page": 46,
"price": 642.0,
"title": "Nulla tincidunt,",
"authorId": 4
},
{
"id": 5,
"description": "tellus non magna. Nam ligula elit, pretium",
"isbn": "Q0V 7Q9",
"page": 86,
"price": 656.0,
"title": "purus, in",
"authorId": 1
},
{
"id": 6,
"description": "a, facilisis non, bibendum sed, est.",
"isbn": "V6Q 8T2",
"page": 57,
"price": 299.0,
"title": "sagittis",
"authorId": 3
},
{
"id": 7,
"description": "suscipit nonummy. Fusce fermentum fermentum arcu. Vestibulum ante ipsum",
"isbn": "Q2T 8C5",
"page": 68,
"price": 891.0,
"title": "ligula. Donec",
"authorId": 8
},
{
"id": 8,
"description": "arcu. Vivamus sit amet risus. Donec egestas.",
"isbn": "R5E 3I4",
"page": 14,
"price": 455.0,
"title": "vel",
"authorId": 6
},
{
"id": 9,
"description": "pede, nonummy ut, molestie in, tempus",
"isbn": "I0W 6N9",
"page": 33,
"price": 874.0,
"title": "lorem semper",
"authorId": 8
},
{
"id": 10,
"description": "sed consequat auctor, nunc nulla vulputate dui, nec",
"isbn": "U4E 5V8",
"page": 7,
"price": 185.0,
"title": "vel arcu.",
"authorId": 4
},
{
"id": 11,
"description": "new book created",
"isbn": "10254857964",
"page": 10,
"price": 20.0,
"title": "new book",
"authorId": 1
},
{
"id": 12,
"description": "new book created",
"isbn": "10254857964",
"page": 10,
"price": 20.0,
"title": "new book",
"authorId": 1
},
{
"id": 13,
"description": "new book created",
"isbn": "10254857964",
"page": 10,
"price": 20.0,
"title": "new book",
"authorId": 1
},
{
"id": 14,
"description": "new book created",
"isbn": "10254857964",
"page": 10,
"price": 20.0,
"title": "new book",
"authorId": 1
},
{
"id": 15,
"description": "new book created",
"isbn": "10254857964",
"page": 10,
"price": 20.0,
"title": "new book",
"authorId": 1
},
{
"id": 16,
"description": "new book created",
"isbn": "10254857964",
"page": 10,
"price": 20.0,
"title": "new book",
"authorId": 1
},
{
"id": 19,
"description": "new book created",
"isbn": "10254857964",
"page": 10,
"price": 20.0,
"title": "new book",
"authorId": 1
}
]
}
}- Create new books

JSON mapping file:
{
"request": {
"method": "POST",
"url": "/v1/book",
"bodyPatterns": [
{ "matchesJsonPath": "$.[?(@.id)]" },
{ "matchesJsonPath": "$.description" },
{ "matchesJsonPath": "$.isbn" },
{ "matchesJsonPath": "$.page" },
{ "matchesJsonPath": "$.price" },
{ "matchesJsonPath": "$.title" },
{ "matchesJsonPath": "$.authorId" }
]
},
"response": {
"status": 201,
"headers": {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "*"
},
"jsonBody": {
"id": 1,
"description": "netus et malesuada",
"isbn": "X4J 5H8",
"page": 120,
"price": 451.0,
"title": "best book",
"authorId": 9
}
}
}URLs can be matched either by equality or by regular expression. For advanced use with Wiremock, we can use Regex matching on path and query to restrict URLs. Additionally, Wiremock provides the concept of scenarios to help simulate the different states of a REST API. This allows us to create tests where the API we use behaves differently depending on the state it is in.
All mapping JSON files are available in the project directory:

Containerize application
The next step is to containerize our API in order to deploy it.
- Dockerfile:
# creates a layer from the openjdk:17-alpine Docker image.
FROM openjdk:17-alpine
MAINTAINER boottechnologies.ci@gmail.com
# cd /app
WORKDIR /app
# Refer to Maven build -> finalName
ARG JAR_FILE=target/spring-stub-server-*.jar
# cp target/spring-stub-server-0.0.1-SNAPSHOT.jar /app/spring-stub-server.jar
COPY ${JAR_FILE} spring-stub-server.jar
# java -jar /app/spring-stub-server.jar
CMD ["java", "-jar", "-Xmx1024M", "/app/spring-stub-server.jar"]
# Make port 8080 available to the world outside this container
EXPOSE 80802. Docker compose
version: '3.8'
services:
api:
build: .
ports:
- '8080:8080'
container_name: mockapiTesting
Let's test our application after deployment.
POST Login:

POST book:

GET books

Conclusion
In this story, We have seen how to build a standalone stub server for stubbing REST APIs using Spring Cloud Contract WireMock.
The complete source code is available on GitHub.
If you enjoyed this story, please give it a few claps 👏 for support.
Thanks for reading!