WireMock: Stubbing & Verifying HTTP Services in Tests
WireMock is an HTTP mock server for Java integration tests. It starts an embedded HTTP server, lets you define stub responses for any URL pattern, and records which requests were received so you can verify them after the fact. It eliminates the need for real external services in CI pipelines.
Contents
Dependency & Setup
Add the following to your pom.xmlbuild.gradle
<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());
}
}
Stubbing GET Requests
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);
}
Stubbing POST Requests
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\"}")));
}
Request Matching
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
Verifying Requests
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")));
}
Fault & Delay Simulation
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)));
CONNECTION_RESET_BY_PEERjava.io.IOExceptionSpring Boot Integration
Use @AutoConfigureWireMockapplication.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/src/test/resources/mappings/