Are you properly testing Service classes that call external REST APIs?

You can technically test them by mocking the HTTP client with Mockito, but the problem is that “no actual HTTP communication occurs.” If you want to verify timeout behavior or how your service handles 5xx errors, Mockito alone won’t cut it.

That’s where WireMock comes in. It spins up a local HTTP server and lets you stub external APIs at the HTTP level. This article walks you through everything from setup to practical stub definitions.

The Difference Between Mockito and WireMock

Let’s clarify the roles first.

Mockito injects mocks at class boundaries. You can mock a RestTemplate Bean to control what exchange() returns, but no actual HTTP socket communication takes place. Network-layer behaviors like connection timeouts or SSL errors cannot be reproduced.

WireMock starts a local HTTP server and returns arbitrary responses to requests at specified paths. Since RestTemplate and WebClient send “real HTTP requests,” you can verify everything including misconfigured HTTP settings and timeout behavior.

The decision is simple: ask yourself whether you need to test including the HTTP layer. Use Mockito when unit tests with JUnit/Mockito are sufficient, and use WireMock when you need real HTTP communication via RestTemplate/WebClient.

Adding the Dependency

wiremock-spring-boot ships with Spring Boot autoconfiguration, keeping setup minimal.

For Maven

<dependency>
    <groupId>org.wiremock.integrations</groupId>
    <artifactId>wiremock-spring-boot</artifactId>
    <!-- Check Maven Central (org.wiremock.integrations:wiremock-spring-boot) for the latest version -->
    <version>3.1.0</version>
    <scope>test</scope>
</dependency>

For Gradle

// Check the wiremock-spring-boot GitHub Releases page for the latest version
testImplementation 'org.wiremock.integrations:wiremock-spring-boot:3.1.0'

Note that com.github.tomakehurst:wiremock-jre8 is a WireMock 2.x artifact — avoid using it in new projects.

Setup with @SpringBootTest + @EnableWireMock

When using wiremock-spring-boot, the primary configuration is combining @SpringBootTest with @EnableWireMock. Since the Spring context starts up, you can inject your Service with @Autowired.

On imports: WireMock 3.x has migrated to the org.wiremock package. In a wiremock-spring-boot 3.x environment, org.wiremock.client.WireMock.* is the recommended import. The old package com.github.tomakehurst.wiremock.client.WireMock.* remains for backward compatibility, but use the new package to avoid confusion when referencing official documentation.

Also, with @EnableWireMock, stub definitions and request history are automatically reset between test methods. Stubs from a previous test will never bleed into the next one.

import static org.wiremock.client.WireMock.*;

@SpringBootTest
@EnableWireMock
class UserServiceTest {

    @Autowired
    private UserService userService;

    @Test
    void getUser_success() {
        stubFor(get(urlEqualTo("/api/users/1"))
            .willReturn(aResponse()
                .withStatus(200)
                .withHeader("Content-Type", "application/json")
                .withBody("{\"id\": 1, \"name\": \"Taro\"}")));

        User user = userService.findById(1L);
        assertThat(user.getName()).isEqualTo("Taro");
    }
}

There is also a @WireMockTest annotation provided by the core WireMock library, but it does not start a Spring context. @Autowired will not work, and you must manually instantiate your Service with new. For tests that require the Spring Boot context, use @SpringBootTest + @EnableWireMock.

Pointing RestTemplate/WebClient Base URL to WireMock

Using @EnableWireMock automatically registers the wiremock.server.port property (this does not happen with @WireMockTest alone). Simply add the following to application-test.properties:

api.base-url=http://localhost:${wiremock.server.port}

On the Service class side, implement it to receive the URL via @Value("${api.base-url}"). See also this article on configuring RestTemplate/WebClient.

Stub Definition Patterns: stubFor Basics

When you want to use a JSON file as a response body, withBodyFile is convenient. The convention is to place files under src/test/resources/__files/.

// Place JSON at src/test/resources/__files/responses/user.json
stubFor(get(urlEqualTo("/api/users/1"))
    .willReturn(aResponse()
        .withStatus(200)
        .withHeader("Content-Type", "application/json")
        .withBodyFile("responses/user.json")));

POST request stubs follow the same pattern:

stubFor(post(urlEqualTo("/api/users"))
    .willReturn(aResponse()
        .withStatus(201)
        .withHeader("Content-Type", "application/json")
        .withBody("{\"id\": 2, \"name\": \"Jiro\"}")));

Reproducing Error Responses (4xx/5xx)

Getting a real API to intentionally return a 500 is difficult. With WireMock, it’s trivial:

@Test
void getUser_throwsExceptionOnServerError() {
    stubFor(get(urlEqualTo("/api/users/1"))
        .willReturn(aResponse().withStatus(500)));

    assertThrows(HttpServerErrorException.class, () -> {
        userService.findById(1L);
    });
}

RestTemplate throws HttpClientErrorException for 4xx and HttpServerErrorException for 5xx. If you’re using WebClient, verify the WebClientResponseException handled in your onStatus callback.

Simulating Timeouts with withFixedDelay

You can use withFixedDelay to intentionally delay a response. Prepare a RestTemplate Bean with a short ReadTimeout for testing and wire it in with @Import.

import static org.wiremock.client.WireMock.*;

@SpringBootTest
@EnableWireMock
@Import(TimeoutTest.TimeoutConfig.class)
class TimeoutTest {

    @Autowired
    private UserService userService;

    @Test
    void getUser_throwsResourceAccessExceptionOnTimeout() {
        stubFor(get(urlEqualTo("/api/users/1"))
            .willReturn(aResponse()
                .withStatus(200)
                .withFixedDelay(3000))); // 3-second delay

        assertThrows(ResourceAccessException.class, () -> {
            userService.findById(1L);
        });
    }

    @TestConfiguration
    static class TimeoutConfig {
        @Bean
        @Primary
        public RestTemplate restTemplate(RestTemplateBuilder builder) {
            return builder
                .setReadTimeout(Duration.ofSeconds(1))
                .build();
        }
    }
}

This is also useful for verifying that retries or fallbacks trigger after a timeout. If your service uses a Resilience4j circuit breaker, you can reproduce the delay with WireMock and verify whether the circuit opens.

Verifying API Calls with verify

Beyond returning stubbed responses, it’s important to verify that “the correct request was actually sent.”

@Test
void getUser_callsCorrectEndpoint() {
    stubFor(get(urlEqualTo("/api/users/1"))
        .willReturn(aResponse().withStatus(200).withBody("{\"id\": 1}")));

    userService.findById(1L);

    verify(1, getRequestedFor(urlEqualTo("/api/users/1"))
        .withHeader("Accept", equalTo("application/json")));
}

Use withRequestBody(containing("...")) to verify POST request bodies. When the call count matters, use verify(exactly(1), ...) or verify(moreThan(0), ...).

Mockito vs. WireMock: When to Use Which

SituationTool to Use
Mocking internal dependencies without HTTPMockito
Real HTTP communication via RestTemplate/WebClientWireMock
Reproducing timeouts and network errorsWireMock
DB integration testingTestcontainers

Combined with integration testing using Testcontainers, you can achieve broad coverage with Testcontainers handling the DB and WireMock handling external HTTP.

Conclusion

WireMock makes it easy to stub external APIs at the HTTP level. You can test not just happy paths but also error cases and timeouts, freeing you from brittle tests that depend on real APIs. Once you add verify to inspect request contents, your external API integration tests will be significantly more robust.