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.

Share

Contents

Dependency & Setup

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.

XML
<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>
Java
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.

Java
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.

Java
@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.

Java
// 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.

Java
@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.

Java
// 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.

Spring Boot Integration

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

XML
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-contract-wiremock</artifactId>
  <scope>test</scope>
</dependency>
Java
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.