Spring Boot Integration Tests With WireMock and JUnit 5

Last Updated:  April 6, 2022 | Published: November 28, 2019

This article showcases a testing recipe for writing integration tests for Spring Boot applications using WireMock and JUnit. In one of the previous blog posts, I demonstrated how to write integration tests with Spring Boot and Testcontainers. The previous post targeted the database setup for integration tests, you might wonder how to stub other dependencies like external systems. For such use cases, we can use WireMock to write integration tests for Spring Boot applications.

Update: Starting with WireMock 2.31.0, WireMock now ships with a JUnit Jupiter extension. This article showcases two ways of integrating WireMock for Spring Boot integration tests. We're going to use Spring Boot 2.6, JUnit 5, WireMock 2.32.0, and Java 17. You can find a complimentary four-part WireMock YouTube series here.

Spring Boot and WireMock Project Setup

First, let's cover the application setup and its dependencies.

The demo application exposes one public HTTP endpoint, runs on Tomcat, and uses the Spring WebClient (part of Spring WebFlux and superseding the RestTemplate) to make HTTP(S) calls to an external system. These requirements result in the following project setup:

For our test dependencies, we include WireMock (wiremock-jre8) and the Spring Boot Starter test (aka. testing swiss-army knife):

In addition to this, we use the Maven Failsafe Plugin to separate the execution of our unit and integration tests:

With this setup, our unit tests run first using the Maven Surefire Plugin. Right after packaging the application as a .jar file, Maven will execute our integration tests. This is a common testing best practice for Maven projects. To make Maven Failsafe Plugin recognize our test as an integration test we have to add the postfix IT (or any other if we configure it) to our test class.

Spring Boot Application Walkthrough

Next, let's have a look at the sample application. The application exposes one public endpoint to retrieve a list of todos:

For the sake of simplicity, the controller returns the result of the WebClient call. To set up this WebClient, we're using the following configuration class:

Outsourcing the base URL with a configuration value (@Value("${todo_base_url}")) will be an essential requirement for testing the application later on. If we hardcode this URL, we can't easily (i.e., without using reflection) override this URL.

By default, the application fetches random todos from a public placeholder API:

Once the application is up and running, the result of http://localhost:8080/api/todos is the following:

Integration Test Setup for WireMock and JUnit 5

As we are about to write the integration tests for this application, we may not want to call the actual HTTP endpoint during the tests. This is less critical for this example, but imagine testing logic that manipulates data within the remote application using HTTP POST or PUT. We don't want this to happen for our tests and rather talk to a stub.

Moreover, if we would connect to the actual remote system for our integration test, the test outcome depends on the availability of the remote service and produces additional load. At the same time, we may also want to test different responses (e.g., body or status code).

We can resolve the described downsides of connecting to the real remote system by using WireMock. With WireMock, we can instrument 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.

WireMock JUnit 5 Spring Boot Setup Example

Let's explore two possible setup variations writing Spring Boot integration tests with WireMock and JUnit 5. For JUnit 4, check out this YouTube video.

Setup Variant 1: WireMock's JUnit Jupiter Extension

The first setup variant to integrate WireMock for a Spring Boot integration test using JUnit 5 is the WireMockExtension. This extension is available starting with WireMock 2.31.0. Previous WireMock versions have to fall back to JUnit 4, a manual server lifecycle, or to setup variant two.

We can use this extension to bind the lifecycle of the WireMock server to the lifecycle of our test. This extension will handle starting and stopping the server for us. We can choose between the declarative (@WireMockTest on top of our test class) and programmatic registration (@RegisterExtension on top of a static/instance field) of the WireMockExtension.

The programmatic approach offers more control over the WireMock server as we can pass a WireMockConfig:

We declare a static field for the WireMockExtension to start the WireMock server before the test class and reuse it for all test methods of a test class. Alternatively, we can use an instance field and get a new WireMock instance for each test method.

As WireMock listens on a random port, we have to dynamically override our todo_base_url after WireMock has started but before our WebClientConfig prepares our WebClient instance.

Therefore, we use a similar technique that we might already know from working with Spring Boot and Testcontainers:

This technique allows us to set a configuration property upon starting the Spring Boot application for testing purposes.

For more information on this extension, head over to the WireMock documentation.

Setup Variant 2: ApplicationContextInitializer

The second option for integrating WireMock involves a Spring ApplicationContextInitializer and is therefore only applicable for testing Spring/Spring Boot applications. We can use this variant when upgrading WireMock version is not possible (for whatever reasons) and/or we want to bind the WireMock server lifecycle to a Spring TestContext.

Spring executes every configured ApplicationContextInitializer upon starting the ApplicationContext. As part of this initializer, we can start the WireMock server, register the server as a bean inside the Spring context and override the configuration value for our todo_base_url:

Using an initializer, we bind the lifecycle of our WireMock server to a specific Spring context. Using a dynamic port, we avoid port clashes when spawning multiple Spring contexts throughout our test suite. We'll share the same WireMock server for all tests that reuse this Spring context.

We activate this initializer using @ContextConfiguration on top of our test class:

As we place the WireMockServer inside our TestContext, we can inject it as any other Spring bean using @Autowired.

With either setup variant one or two in place, we can now move on and write the actual integration test(s).

Writing Integration Tests with WireMock and JUnit 5

With the WireMock server setup in place, we can now start writing our Spring Boot integration tests.

Spring Boot auto-configures the WebTestClient for us once we use @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT). With this client, we can access our own REST API, which internally fetches data from the placeholder API (i.e., WireMock during the test) and verify the response afterward.

Before we hit our controller endpoint with the WebTestClient, we have to stub the HTTP response from the external system:

When stubbing a fake HTTP response from WireMock using stubFor, we have to match the HTTP method and path that our application will invoke. The wither methods (e.g., withHeader())  give us a flexible way to construct the stubbed HTTP response.

The actual test verification happens when reaching our backend with the WebTestClient. The fluent API of this test client allows defining both the HTTP request and our expectations:

As we're sharing the same WireMock server instance for our tests (unless we register the WireMockExtension as an instance field), we have to reset our stubbing setup between our tests:

With .resetAll() we're resetting the WireMockServer to its default settings and remove all HTTP stubs. This gives us a clean setup for each test while slightly improving our build times as we don't start a new WireMock server (remember this starts a fully-fledged Jetty) for each test.

HTTP Stubbing Options with WireMock

As we're in full control of the HTTP response of the remote system, we can also test non-happy-paths to understand and verify how our application reacts:

The test above simulates an HTTP 403 response for our placeholder API. As we don't have any exception handling in place for our demo application, we're propagating the exception. With WireMock, we can even test slow responses by specifying a delay using .withFixedDely():

You can find the source code for this example on GitHub.

For more practical hands-on testing recipes for writing unit, integration, and end-to-end tests for Spring Boot Applications, consider enrolling for the Testing Spring Boot Applications Masterclass.

PS: WireMock as a testing tool is also introduced in further detail as part of the Java Testing Toolbox.

Have fun writing integration tests with Spring Boot, WireMock, and JUnit 5 with this recipe,

Phil

>