[Spring Boot] Using WireMock and MockWebServer for Spring WebFlux Integration Tests

Jay Kim
3 min readJul 22, 2023

This article covers how to configure WireMock and MockWebServer for a Spring WebFlux application to stub for any external APIs the application might need.

Implementation

Currently, WebClient is used for calling the external APIs.

@Configuration
public class HttpProxyConfiguration {

@Value("${tracker.url}")
private String trackerUrl;

@Bean
TrackerClient trackerClient(WebClient.Builder builder) {
var wc = builder.baseUrl(trackerUrl).build();
var wca = WebClientAdapter.forClient(wc);
return HttpServiceProxyFactory.builder()
.clientAdapter(wca)
.build()
.createClient(TrackerClient.class);
}
}

WireMock

Dependency

For setting up WireMock, spring-cloud-contract-wiremock dependency will be used. Because this is a spring-cloud dependency, the version and dependency management for spring-cloud have to be configured as well.

// plugins, group, version, java, repositories

extra["springCloudVersion"] = "2022.0.4"

dependencies {
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-webflux")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("io.projectreactor:reactor-test")
testImplementation("org.springframework.cloud:spring-cloud-contract-wiremock")
}

dependencyManagement {
imports {
mavenBom("org.springframework.cloud:spring-cloud-dependencies:${property("springCloudVersion")}")
}
}

TestCode

Here are the steps on how this works:

  • @AutoConfigureWireMock is used to start a WireMock server as part of the Spring Application Context and WireMock server properties will be mapped to wiremock.server.
  • Then, the external API endpoint can be replaced to the started WireMock server by overriding the application properties to http://localhost:${wiremock.server.port}.
  • Start adding stubs for every API endpoint using wireMockServer.stubFor().
  • Happy Testing!
import com.github.tomakehurst.wiremock.WireMockServer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.reactive.server.WebTestClient;

import java.io.IOException;

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.post;
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.springframework.http.HttpStatus.OK;

@AutoConfigureWireMock(port = 0)
@TestPropertySource(properties = {
"tracker.url=http://localhost:${wiremock.server.port}"
})
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class JourneyTests {

@Autowired
WebTestClient wtc;

@Autowired
WireMockServer wireMockServer;

@Test
void test() throws IOException {
wireMockServer.stubFor(get(urlPathEqualTo("/first_api_path"))
.willReturn(aResponse()
.withStatus(OK.value())
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.withBody(new ClassPathResource("first_api_call.json").getContentAsString(UTF_8))));

wireMockServer.stubFor(post(urlPathEqualTo("/second_api_path"))
.willReturn(aResponse()
.withStatus(OK.value())
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.withBody(new ClassPathResource("second_api_call.json").getContentAsString(UTF_8))));


List<Project> response = wtc.get()
.uri("/api/projects")
.exchange()
.expectBodyList(Project.class)
.returnResult()
.getResponseBody();


assertThat(response.size()).isEqualTo(1);
// ... other assertions
}
}

MockWebServer

Dependency

For setting up MockWebServer, a couple of dependencies need to be added.

// plugins, group, version, java, repositories

dependencies {
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-webflux")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("io.projectreactor:reactor-test")
testImplementation("com.squareup.okhttp3:okhttp")
testImplementation("com.squareup.okhttp3:mockwebserver")
}

Test Code

Here are the steps on how this works:

  • First, ask Java for an unused port using new ServerSocket(0). We will use this port for spinning up a new MockWebServer.
  • Then, change the external API url for this test using @DynamicPropertySource.
  • Start MockWebServer on that unused port which we got from the first step.
  • Start adding stubs for the external APIs using mockWebServer.enqueue().
  • Happy testing!
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.web.reactive.server.WebTestClient;

import java.io.IOException;
import java.net.ServerSocket;
import java.util.List;

import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.springframework.http.HttpStatus.OK;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class JourneyTests {
static int MOCK_SERVER_PORT;

static {
try (var serverSocket = new ServerSocket(0)) {
MOCK_SERVER_PORT = serverSocket.getLocalPort();
} catch (IOException e) {
throw new RuntimeException(e);
}
}

MockWebServer mockWebServer;

@Autowired
WebTestClient wtc;

@DynamicPropertySource
static void trackerProperties(DynamicPropertyRegistry registry) {
registry.add("tracker.url", () -> "http://localhost:" + MOCK_SERVER_PORT);
}

@BeforeEach
void beforeEach() throws IOException {
mockWebServer = new MockWebServer();
mockWebServer.start(MOCK_SERVER_PORT);
}

@AfterEach
void afterEach() throws IOException {
mockWebServer.shutdown();
}

@Test
void test() throws IOException {
var firstJsonResponse = new MockResponse()
.setResponseCode(OK.value())
.setBody(new ClassPathResource("first_api_call.json").getContentAsString(UTF_8))
.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
mockWebServer.enqueue(firstJsonResponse);

var secondJsonResponse = new MockResponse()
.setResponseCode(OK.value())
.setBody(new ClassPathResource("second_api_call.json").getContentAsString(UTF_8))
.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
mockWebServer.enqueue(secondJsonResponse);


List<Project> response = wtc.get()
.uri("/api/projects")
.exchange()
.expectBodyList(Project.class)
.returnResult()
.getResponseBody();


assertEquals(1, response.size());
// ... other assertions
}
}

--

--