Testing Spring Boot Application using WireMock and JUnit 5

Suman Das
Crux Intelligence
Published in
13 min readJul 26, 2022
Spring Boot with WireMock

In this world of Microservices, we want to build and ship things faster. When we ship our code, we want to make sure we ship well tested quality code. Hence, testing our code becomes very important. Since testing our code is so important, today we have many tools to help us write efficient tests.

In this article, we will discuss one such tool named Wiremock along with Junit 5, that we can use to simulate other services on which our app depends during integration testing of the Spring Boot application.

Let us start with WireMock.

What is WireMock?

WireMock is a library for stubbing and mocking web services. It constructs an HTTP server that acts as an actual web service. In simple terms, it is a mock server that is highly configurable and has the ability to return recorded HTTP responses for requests matching criteria.

WireMock has a feature called Record and Playback. It can create stub mappings automatically from requests it has received. Combined with its proxying feature this allows us to “record” stub mappings from interaction with existing APIs. When in action, we have the option to either call the external API and capture the response or use one of the earlier recorded stubs to reply to the caller without calling the external API.

WireMock Flow

Why do we need WireMock?

Suppose our SpringBoot Application calls external applications and this call to external services bears some cost. However, we can’t test the application without calling the external services. In this case, we can use WireMock to mock the external applications while testing the REST API that we are developing in SpringBoot.

WireMock can be used as a library or as a standalone process. In this tutorial, we will use it as a library. Later, we will see how we can use WireMock to mock the external applications while testing the REST API that we are developing using SpringBoot.

What is JUnit 5?

JUnit is a unit testing framework for the Java programming language. JUnit has been important in the development of test-driven development and is one of a family of unit testing frameworks, which is collectively known as xUnit that originated with SUnit.

Unlike previous versions of JUnit, JUnit 5 is composed of several different modules from three different sub-projects.

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

The JUnit Platform serves as a foundation for launching testing frameworks on the JVM. It also defines the TestEngine API for developing a testing framework that runs on the platform. Furthermore, the platform provides a Console Launcher to launch the platform from the command line and the JUnit Platform Suite Engine for running a custom test suite using one or more test engines on the platform.

JUnit Jupiter is the combination of the new programming model and extension model for writing tests and extensions in JUnit 5. The Jupiter sub-project provides a TestEngine for running Jupiter based tests on the platform.

JUnit Vintage provides a TestEngine for running JUnit 3 and JUnit 4 based tests on the platform. It requires JUnit 4.12 or later to be present on the classpath or module path.

Let’s create a simple REST API using Spring Boot and JAVA Config. Then we will perform an integration test of our application using the JUnit 5 and WireMock.

Prerequisites

Before we begin, we must ensure that the following pre-requisites are installed on the local machine:

Steps to Build

  1. Set up the Spring Boot Project
  2. Include Additional Maven Dependencies
  3. Furnish the Configurations
  4. Define a DTO Class
  5. Define the Rest Controller Endpoints
  6. Construct the Service Layer
  7. Start the Application
  8. Test the Application with Swagger-UI
  9. Add Integration Test
  10. Conclusion

1. Set up the Spring Boot Project

For this tutorial, we are using JDK 17 and Spring Boot 2.7.1 project. Use start.spring.io and create a sample project using the below artifacts:

  • Spring Web: Build web, including RESTful, applications using Spring MVC. Uses Apache Tomcat as the default embedded container.
Creating Sample Project

2. Include Additional Maven Dependencies

Add the additional dependencies, shown below, to our project to enable Swagger, Lombok, WireMock, and JUnit for our application.

<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.6.9</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-standalone</artifactId>
<version>2.27.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>

Swagger: Swagger is an open-source tool to develop APIs, interact with APIs, and mainly generate documentation for APIs.

Lombok: Java annotation library which helps to reduce boilerplate code.

WireMock and JUnit: for Integration testing.

3. Furnish the Configurations

3.1 Configure RestTemplate

To call an external API we need to create a RestTemplate instance. Let’s start with that. Spring Boot >= 1.4 no longer automatically defines a RestTemplate but instead defines a RestTemplateBuilder allowing us more control over the RestTemplate that gets created. We can inject the RestTemplateBuilder as an argument in our @Bean method to create a RestTemplate:

@Configuration
public class ApplicationConfig {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
// Do any additional configuration here
return builder.build();
}
}

3.2 Configure application.yml

application.yml file contains all the configuration required by our application during runtime. It also contains externalized configurations. Here, we will just specify our application name and externalize the URL for the dependent service. We will use this URL later to connect to the external service.

spring:
application:
name: "Sample Spring Boot Application"

universitiesBaseURL: http://universities.hipolabs.com

3.3 Configuration to read external properties

We can bind the properties defined in the application.yml file to a Java class using @ConfigurationProperties. This annotation allows to map the entire properties and yml files into an object easily. The @ConfigurationProperties annotation takes a prefix parameter, and binds all the properties with the specified prefix to the Java class.

@Configuration
@ConfigurationProperties(prefix = "wiremock-config")
@Data
@Profile("integration")
public class WireMockConfig {
private List<WireMockProxy> proxies;
}

In this article, we will use the WireMockConfig class to read all the configurations related to WireMock defined in application-integration.yml file. We will be defining this application-integration.yml file when we write the integration tests in Step 9. We will need these configurations during the Integration test.

4. Define a DTO Class

In this application, we will call some external API and then map the JSON response of the API to this DTO UniversityDTO.

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class UniversityDTO {
private List<String> domains;
private List<String> web_pages;
private String name;
private String country;
private String alpha_two_code;
}

5. Define the Rest Controller Endpoints

Finally, let us write some APIs that can be exposed to the clients. This API will call some external APIs and return the results to the client. Here, we are using one of the free API listed in Apipheny for demonstration purposes. We will use the Universities List API to get a list of universities in a specified country. We will not be validating the response, as we are just simulating the scenario where in our application we may need to call some APIs, do certain processing on top of that and then return the results.

UniversityController

In the above code from line 22to 23 we are calling the method getUniversitiesForCountry of the service class, for fetching the list of all universities for the given input. Then we return the result to the caller.

Next, let’s see how we are getting the data from the external API.

6. Construct the Service Layer

Let us construct the Service Layer for our application. The service layer is required for encapsulating the business logic and also for centralizing the data access.

We will add a Service class named UniversityService inside the sub-package service of the root package. This class will contain the method getUniversitiesForCountry that will be used to fetch the universities for the given country.

UniversityService

As we can see above, UniversityService does not have much business logic other than calling some external service, and map the response to UniversityDTO and then return the response to the caller.

7. Start the Application

Till now we have written all the code required for our application to run. We will execute the below maven command from the root directory of the project to compile and build the jar.

mvn clean package

Once the build is complete then we can start the application. Here, we will use IntelliJ to run the application.

Starting the application

As we can see above, the application started successfully on port 8080.

8. Test the Application with Swagger-UI

Let’s test our universities endpoint using Swagger-UI. Once the application started successfully, we can see all the endpoints for our application using the Swagger-UI. Open the swagger UI and check the endpoint. The Swagger will open at the below URL.

http://localhost:8080/swagger-ui/index.html
Swagger-UI

Let’s now test the endpoint for some sample countries.

Sample Test using Swagger

As we can see above, we got the response for the country India. So our application is up and running. Similarly, it can also be tested for other countries.

9. Add Integration Test

Let’s start writing some Integration tests for this application. During Integration tests, we don’t want to call the actual HTTP endpoint. This is less critical for this example, but imagine a scenario where we manipulate the data within the remote application using HTTP POST or PUT. We don’t want this to happen during our tests and instead talk to a stub.

Also, if we connect to the actual remote system for our integration test, the test outcome depends on the availability of the remote service and produces additional dependency. At the same time, we may also want to test different responses (e.g., for different scenarios and status codes).

We can resolve the above challenges of connecting to the external API during integration testing using WireMock. With WireMock, we can capture the actual response for a given URL and set the body and status code to whatever we need. Internally, WireMock spawns a local Jetty server that handles the remote calls during our integration tests.

9.1 Add the Configuration File

First, we need to add a configuration file name : application-integration.yml inside the “src/main/resources” folder. This file will contain all the configurations, which we need during the Integration test or we want to override during the Integration tests. Here, the term integration in the file name refers to the profile name which we will use during the tests. We have already defined the Java class WireMockConfig in step 3.3 above to read these configurations during runtime.

application-integration.yml

The file will contain the below configurations:

Integration configurations

As we have seen above in step 5 we are calling the Universities List API to get a list of universities for a given country. So during our Integration tests, we want WireMock to listen to this URL and return responses from the stubs if present. Here, we are specifying the Mock Server to run at port : 9081 for the base URL : http://universities.hipolabs.com. Any request made to this URL will be intercepted by this Mock Server and will return a response from the source or stubs depending upon the value of the property : recording .

If recording is true then it will call the source and capture the response and return the response to the caller. If the recording is false then it will look into the mappings folder for the captured stubs. If there is a stub matching the required criteria, then it will return a response from the stub to the caller.

Note: We can see above that we have overridden the universitiesBaseURL to the proxy URL.

Here, we have taken proxies in a list format and not as a single entry, although we are calling only one API in this case. This is because if we have to call multiple external APIs, then we can just append the configuration below the current one and it will work.

9.2 Add Test Directories

We need to create some directories inside the “src/test/resources” folder for WireMock to record files.

test directories

As we can see above, we need to add the following directories for the integration test:

  • __file (contains response errors and messages JSON)
  • mappings (contains request and response JSON) directories that are needed by WireMock for recording purposes
  • recorded directory is used to capture the actual response of the REST API which we will use to compare against our Integration test results

Let’s now start writing the Integration test for the REST API which we exposed earlier.

9.3 Add the Test Case

Let’s start with adding a new Integration Test class with the file name : SpringBootWiremockApplicationTests inside “src/test/java” folder.

Integration Test file
SpringBootWiremockApplicationTests

In the above code:

  • Set the active profile: In line 1 we are directing our tests to use the profile: integration by annotating the test class with the @ActiveProfiles annotation. By setting integration profile as the active profile, the tests will read the configurations from the file application-integration.yml which we defined in step 9.1.
  • Look up main configuration class: In line 2 @SpringBootTest annotation tells Spring Boot to look for the main configuration class (one with @SpringBootApplication, for instance) and use that to start a Spring application context.
  • Configure auto-configuration of MockMVC: We also have added the annotation @AutoConfigureMockMvc in line 4 because we’ll use MockMvc to call the “/api/university “ endpoint for the test. We could have called the service method directly depending on what kind of test we wanted to implement. In this article, we are trying to demonstrate an example of an integration test by calling the REST endpoints directly with MockMvc.
  • Start WireMock server and record: From line 24 to 36 we defined a method called startRecording, which will be executed before every test case. It reads the configuration file defined in step 9.1. If recording flag is turned on then it will start the WireMock server in recording mode, else it will start in regular mode.
  • Shut down WireMock server and stop recording: From line 39 to 48 we defined a method called stopRecording which will be executed after every test case. If recording flag is turned on then it will stop the recording before shutting down the WireMock server in recording mode or else it will just stop the server in regular mode.
  • Provide WireMock server configuration: From line 124 to 133 we have provided some configuration for the WireMock server. It will start intercepting all the requests specified at the wiremock-config.proxies.url. In this tutorial we have specified only one URL : http://universities.hipolabs.com. If the recording is enabled then it will persist the stubs in the mappings folder. While matching against the stubs it will ignore the arrayOrder and extraElements in the response JSON.
  • Define integration test case: From line 51 to 63 we defined the integration test case for the REST API “/api/university”. Here we have used the parameterized test case of JUnit 5. MockMvc is used to call the “/api/university “ endpoint. It then saves the response temporarily in JSON format to compare against the expected response. Here, we are using JsonAssert to compare the expected and actual JSON responses. Once the test case is successful then the current file is cleaned up automatically.

9.4 Record the Test Case

Let’s now see how easily we can record the response of external API and use it during the Integration test. To turn on the recording mode we just need to set recording : true in the configuration defined in step 9.1 above.

recording enabled

Now run the test case. When we run the test case, the response of the external API will be captured inside the mappings folder. The expected response of our API will be captured inside the recorded folder.

recording the test case
running test case in recording mode
recorded files captured

9.5 Replay the Test Case

Now everything is in place. We have the Integration Test case ready to be executed. We have all the recorded files in place. Let’s now run the Test case with recording: false .

recording disabled

With recording off, WireMock will try to search the mappings folder for one of the matched responses for the given request. In case it is able to find the response then will return that, else, it will throw an exception saying it’s not able to get any stubbed response.

replay the test case
Running the test case with recording off

Let’s modify the recorded response manually to see what happens when WireMock is not able to find the matched response.

update the captured response

Let’s say we update the country name in the response to india1 from india . to simulate the scenario when we don’t have the recorded file for a given request.

Now let’s run the test case again with recording: false

no recording found

As we can see above, when we run our test case after modifying the response, we got the error Request was not matched. This time WireMock is not able to find any recordings. It also displayed the closest match it can find.

Conclusion

In this article, we saw how we can leverage WireMock in Spring Boot applications to record the requests and responses for external API for various scenarios, and then we can use that recorded files during Integration Test.

If you would like to refer to the full code, do check:

References & Useful Readings

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Crux Intelligence
Crux Intelligence
Suman Das
Suman Das

Written by Suman Das

Tech Enthusiast, Software Engineer

Responses (2)