Contents
- Dependency & Setup
- Stubbing GET Requests
- Stubbing POST Requests
- Request Matching
- Verifying Requests
- Fault & Delay Simulation
- Spring Boot Integration
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.