Introduction
Mocks are present in any modern development process to speed up software delivery.
Your API service has a dependency on another API, internally or a 3rd party one. There are mocks making sure everything is working as expected at any new change, no matter if it’s in the dependency or not. You work in a good environment, and you feel that something is still missing.
When we need to look out outside the service bubble, which means unit and integration tests, there are some challenges in testing the application. You will learn how to extend the testing coverage and have confidence in delivering the best software by introducing a new testing approach.
Note!
We had a similar article about it the last year, but focusing on the unit test: https://www.javaadvent.com/2021/12/improving-quality-by-mocking-apis-with-wiremock.html
So, you can get spectacular examples of how to apply it in the unit layer.
The problem we are trying to solve
Scenario
There’s an API called credit-simulator-api (Simulations API) which will perform normal CRUD operations to get a loan. This API depends on another one which will first check for restrictions, so we can only continue if there is no restriction. Let’s name it credit-restriction-api (Restrictions API) and consider it a 3rd party API.
As it’s a 3rd party library, you still need to use it for testing. Remember that you have unit tests mocked for this, but what about integration tests and how other teams, who also use this library, will use it?
You need to test this integration within this 3rd party, all well other internal services. You orchestrate different services to test your application in a Kubernetes platform, where everything is delivered as a Docker image.
How can we test the credit-simulator-api as we still don’t have access to the 3rd party library, where more teams probably have the same problem?
How can we test the consumption of the services we created by either a Web App or Mobile App?
Technical scenario
The credit-simulator-api will save a loan simulation through a POST request, which will check before saving it if there’s any restriction using the credit-restriction-api (the 3rd party API). If there’s a restriction the API won’t act meaning no loan simulation will be saved.
The key attribute to check for a restriction is called cpf.
In the credit-restriction-api case, the response without a restriction is an HTTP 404 (not found any restriction), whereas the one with restriction is an HTTP 200 with a message in the response. This will be translated by an HTTP 403 in the credit-simulator-api with a message in the response.
How to solve this problem
The bad practice here would be all the services, that need to use the Restrictions API 3rd party library, create their own solution, and increase the general maintenance.
One of the solutions is to create a “global mock” that can be used not only by the credit-simulator-api in isolation but by any other service that needs it, as well as the usage in persistent contexts. That’s why we will talk about the service virtualization approach.
Service Virtualization
This approach will emulate/simulate the behavior of specific applications, focusing more on the API-driven ones, most of the time cloud-based. It solves the dependency gap, during the development process where we can exercise the whole application exercise this behavior of a real component which is required to test and deliver the product.
In short, it created a virtual service to simulate real behavior.
It has innumerable benefits using it, such as:
- provide an endpoint for not completed APIs
- provide an API for the 3rd parties dependencies you can’t control
- when needed to be consumed from different perspectives (backend, frontend, test)
How Wiremock can solve it?
The focus here is not to explain how Wiremock works, because we have the official documentation for that, and the previous years’ post about it.
The way Wiremock will solve it is in the approach! Instead of using it in the service, as a support tool to write the mocks, we will create a “persistent mock” server which will reply based on certain conditions.
We will create an approach that will be “Dockerized”, so we can use it in any orchestration system, using the Stubbing & Verifying feature.
Wiremock internals
When we don’t use the Wiremock Java library, we can use it via the JSON API, creating request and response JSON files that will match the expected request and responses.
You can find it in the official documentation, but it’s good to make it clear: we can have a folder called mapping, which will contain the requests that will be matched and the __files which will contain the response.
Basically, Wiremock will understand the matched request and reply accordingly base on the mappings we have defined.
How to create the request and response?
The normal method would be either creating it manually if you have some previous experience with Wiremock or using the Record & Playback feature to proxy all the requests and responses and let the tool create it for you. Instead of explaining it we will see the end files, and understand how it will reply to our application.
In this article, we will create it without the Record & Playback to make things shorter and clear.
Technical Solution
Code explanation
We will use the credit-simulator-api project to illustrate the problem, also creating the solution using Wiremock.
The main focus is on the controller, where the POST method first checks if there is a restriction, consuming the Restrictions API using the checkForRestriction() method.
The private method checkForRestriction() hit the Restrictions API endpoint. When the response is null, which means that the return is HTTP 404 meaning no restriction, nothing will happen. When the response is HTTP 200 meaning a restriction, a custom exception will be thrown.
Solving this problem with Wiremock
The main goal is to solve the issue above when the mock is available only in the unit layer. We need to create two mappings using Wiremock: one for the restriction and another for the simulation.
We will exercise only the negative scenario, where the simulation will return HTTP 403 because the CPF we are using has a restriction, remember about the image at the beginning of this article.
When this is the case the Restrictions API returns HTTP 200 confirming that a restriction was found.
There’re two main mappings we must do:
- Simulation, returning HTTP 403
- Restriction, returning HTTP 200 and the appropriate message
The mappings for Wiremock will look like the following.
Mapping for the Simulation API
This mapping will match the POST method for the URL /api/v1/simulations/ when the cpf attribute in the response body is 9709326014, returning the HTTP 403 and the response defined in the file simulation_with_restriction_response.json.
This is the response matched in the “bodyFileName” : “simulation_with_restriction_response.json”:
Mapping for Restrictions
This mapping will match the GET method for the URL /api/v1/restrictions/97093236014, returning the HTTP 200 and the response defined in the file restrictions_response_200.json.
This is the response matched in the “bodyFileName” : “restrictions_response_200.json”:
Now we have this “global mock” which means the service virtualization approach, ready for use.
Building a Docker image
A better solution, instead of running it standalone using the Wiremock jar file, would be the creation of a Docker image containing the files and the standalone process. We will divide it into a Dockerfile to generate the image and a docker-compose file to run it locally, for educational purposes. Remember that you can generate and push. custom docker image as a real solution.
The Dockerfile is based on an alpine Java 8 version to reduce the image size. It downloads the Wiremock java standalone jar file, enabling the internal port as 8088, starting it in a verbose mode in the ENTRYPOINT.
In the docker-compose.yml we will build the docker image based on the Dockerfile we have defined above, plus copy the response files from the __files folder, as well as the mapping files from the mappings folder into the Wiremock inside the image.
This will make available the mappings globally when the Docker image is running. You can run it during your service build and, later, create a persistent image to use in the Kubernetes platform.
Project example
Now we will put all these examples into practice.
Simulating the necessity of the Wiremock Service Virtualization
The Simulations API exists, and we will use it to get to the error message where we need the mock to use the API properly.
- Clone the credit-simulator-api project
- Run the following command in your Terminal to start the application:
mvn spring-boot:run
Now, try to create a new Simulation using the POST request. You can do it using the following command:
Alternatively, you can access the OpenAPI spec using the following URL when the application is running, and “Try it out” the POST request: http://localhost:8089/swagger-ui/index.html
The result of this request is an HTTP 500, containing plain text which contains the following:
The following error was encountered while trying to retrieve the URL:
http://localhost:8089/api/v1/simulations/
This is happening because the controller for the POST request has a call to the Restrictions API, which is unavailable.
How to add the Wiremock Service Virtualization solution?
There’s already a solution published for that, the same one explained in the “Solving this problem with Wiremock” section. To exercise it you need to:
- Clone the wiremock-credit-restriction-api
- Start your Docker application
- Run the following command in your Terminal, to build the Docker image and start it
docker-compose up --build
You will see the Wiremock banner.
Now, make the same request to the Simulations API:
You will now receive the HTTP 403 with the following response body:
{
"message": "CPF has a restriction"
}
Congratulations! Now you have the service virtualization approach running inside a Docker container that can be shared across the teams.
Important mention about the example projects
In the credit-simulator-api you can see that the application.properties has the host, port, and path configuration for the Restrictions API. The most important correlation here is that the Wiremock in the Docker image is also responding at the same host and port defined in the file.
The wiremock-credit-restriction-api contains all the necessary files to start the Docker image using the mappings created. Remember that the mappings and responses, ideally, must be placed in this context to avoid different paths for these files.