Contents

Add the following to your pom.xml (Maven) or build.gradle (Gradle). Spring Boot manages compatible versions automatically when you use the Spring Boot BOM.

<dependency> <groupId>org.wiremock</groupId> <artifactId>wiremock-standalone</artifactId> <version>3.9.1</version> <scope>test</scope> </dependency> <!-- Spring Boot — use the Spring Cloud Contract WireMock starter --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-wiremock</artifactId> <scope>test</scope> </dependency> import com.github.tomakehurst.wiremock.junit5.WireMockExtension; import org.junit.jupiter.api.extension.RegisterExtension; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; class PaymentClientTest { // Starts WireMock on a random port before each test, stops after @RegisterExtension static WireMockExtension wm = WireMockExtension.newInstance() .options(wireMockConfig().dynamicPort()) .build(); PaymentClient client; @BeforeEach void setUp() { // Point the client at the WireMock server's base URL client = new PaymentClient(wm.baseUrl()); } }

The class below shows the implementation. Key points are highlighted in the inline comments.

import static com.github.tomakehurst.wiremock.client.WireMock.*; @Test void returnsUserForGetRequest() { wm.stubFor(get(urlEqualTo("/api/users/1")) .willReturn(aResponse() .withStatus(200) .withHeader("Content-Type", "application/json") .withBody(""" { "id": 1, "name": "Alice", "email": "alice@example.com" } """))); User user = client.getUser(1L); assertThat(user.name()).isEqualTo("Alice"); } @Test void returnsNotFoundFor404() { wm.stubFor(get(urlPathMatching("/api/users/\\d+")) .willReturn(aResponse() .withStatus(404) .withBody("{\"error\":\"not found\"}"))); assertThatThrownBy(() -> client.getUser(999L)) .isInstanceOf(UserNotFoundException.class); }

The class below shows the implementation. Key points are highlighted in the inline comments.

@Test void createsOrderOnPost() { wm.stubFor(post(urlEqualTo("/api/orders")) .withHeader("Content-Type", containing("application/json")) .withRequestBody(matchingJsonPath("$.product", equalTo("Widget"))) .willReturn(aResponse() .withStatus(201) .withHeader("Content-Type", "application/json") .withBody(""" { "id": "ord-100", "product": "Widget", "status": "PENDING" } """))); Order created = client.createOrder(new CreateOrderRequest("Widget", 2)); assertThat(created.id()).isEqualTo("ord-100"); assertThat(created.status()).isEqualTo("PENDING"); } @Test void returnsSequentialResponses() { // Different response on each successive call wm.stubFor(get(urlEqualTo("/api/status")) .inScenario("polling") .whenScenarioStateIs("Started") .willReturn(okJson("{\"status\":\"PROCESSING\"}")) .willSetStateTo("done")); wm.stubFor(get(urlEqualTo("/api/status")) .inScenario("polling") .whenScenarioStateIs("done") .willReturn(okJson("{\"status\":\"COMPLETED\"}"))); }

WireMock supports rich request matching — URL patterns, query parameters, headers, cookies, and JSON/XML body matchers.

// URL patterns get(urlEqualTo("/exact/path")) get(urlPathEqualTo("/path/only")) // ignores query params get(urlPathMatching("/api/items/\\d+")) // regex on path get(urlMatching("/api/.*\\?.*page=\\d+")) // regex on full URL // Query parameters get(urlPathEqualTo("/api/products")) .withQueryParam("category", equalTo("electronics")) .withQueryParam("page", matching("\\d+")) // Headers get(anyUrl()) .withHeader("Authorization", matching("Bearer .+")) .withHeader("X-Tenant-Id", equalTo("acme")) // JSON body matchers post(urlEqualTo("/api/orders")) .withRequestBody(matchingJsonPath("$.quantity", greaterThan(0))) .withRequestBody(matchingJsonPath("$.items[*].sku", containing("WGT"))) .withRequestBody(equalToJson(""" { "product": "Widget", "quantity": 5 } """, true, true)) // ignore array order, ignore extra fields

The class below shows the implementation. Key points are highlighted in the inline comments.

@Test void verifiesExactCallCount() { wm.stubFor(post(urlEqualTo("/api/notifications")).willReturn(ok())); notificationService.notifyAll(List.of("a@b.com", "c@d.com")); // Exactly 2 POST requests were made wm.verify(2, postRequestedFor(urlEqualTo("/api/notifications"))); } @Test void verifiesRequestBody() { wm.stubFor(post(urlEqualTo("/api/audit")).willReturn(ok())); auditService.log("USER_DELETED", 42L); wm.verify(postRequestedFor(urlEqualTo("/api/audit")) .withRequestBody(matchingJsonPath("$.event", equalTo("USER_DELETED"))) .withRequestBody(matchingJsonPath("$.userId", equalTo("42"))) .withHeader("Content-Type", containing("application/json"))); } @Test void verifiesNoCallsMade() { // Act — operation that should NOT hit the payment service orderService.cancelFreeOrder(orderId); wm.verify(0, postRequestedFor(urlEqualTo("/api/payments/charge"))); }

Simulate real-world failure conditions — connection drops, timeouts, and slow responses — to test your client's resilience and retry logic.

// Simulate a network-level connection reset wm.stubFor(get(urlEqualTo("/api/data")) .willReturn(aResponse().withFault(Fault.CONNECTION_RESET_BY_PEER))); // Simulate a read timeout — sends headers but hangs on body wm.stubFor(get(urlEqualTo("/api/slow")) .willReturn(aResponse() .withStatus(200) .withFixedDelay(5000))); // 5-second delay // Random delay between 200 ms and 800 ms wm.stubFor(get(anyUrl()) .willReturn(aResponse() .withStatus(200) .withLogNormalRandomDelay(200, 0.1))); // Return a malformed response wm.stubFor(get(urlEqualTo("/api/broken")) .willReturn(aResponse().withFault(Fault.MALFORMED_RESPONSE_CHUNK))); Pair fault simulation with your circuit breaker tests. A CONNECTION_RESET_BY_PEER fault causes java.io.IOException on the client side — verify that your Resilience4j or Spring Retry configuration triggers the fallback correctly.

Use @AutoConfigureWireMock from Spring Cloud Contract to start WireMock inside the Spring test context and automatically inject its port into application.properties.

<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-wiremock</artifactId> <scope>test</scope> </dependency> import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock; import org.springframework.beans.factory.annotation.Autowired; import static com.github.tomakehurst.wiremock.client.WireMock.*; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = "payment.service.url=http://localhost:${wiremock.server.port}") @AutoConfigureWireMock(port = 0) // random port — injected into wiremock.server.port class PaymentIntegrationTest { @Autowired OrderService orderService; // uses real Spring context with WireMock as payment backend @Test void chargesPaymentOnOrder() { stubFor(post(urlEqualTo("/charge")) .willReturn(okJson("{\"transactionId\":\"txn-42\"}"))); Order order = orderService.placeWithPayment("Widget", 1, 9.99); assertThat(order.transactionId()).isEqualTo("txn-42"); verify(1, postRequestedFor(urlEqualTo("/charge"))); } }

Stub files can also be placed in src/test/resources/__files/ (response bodies) and src/test/resources/mappings/ (JSON stub definitions) and WireMock loads them automatically at startup.