Spring Boot cookbook · Intermediate
Test external API integrations with WireMock
Test your RestClient-based integration with a payment API, including errors and timeouts, without calling the real service.
Approach
Start WireMock on a random port, point the client's base URL at it via @DynamicPropertySource, stub success, error and delay responses, and assert on how your code handles each. Verify requests (headers, body) where they matter.
Solution
@SpringBootTest
class RatesClientTest {
@RegisterExtension
static WireMockExtension wm = WireMockExtension.newInstance().options(wireMockConfig().dynamicPort()).build();
@DynamicPropertySource
static void props(DynamicPropertyRegistry r) {
r.add("rates.base-url", wm::baseUrl);
}
@Autowired RatesService rates;
@Test
void returnsRate() {
wm.stubFor(get(urlEqualTo("/rates/USD/EUR"))
.willReturn(okJson("{\"rate\": 0.92}")));
assertThat(rates.rate("USD", "EUR")).isEqualByComparingTo("0.92");
wm.verify(getRequestedFor(urlEqualTo("/rates/USD/EUR")).withHeader("Authorization", matching("Bearer .+")));
}
@Test
void mapsTimeoutToDomainError() {
wm.stubFor(get(anyUrl()).willReturn(ok().withFixedDelay(6_000)));
assertThatThrownBy(() -> rates.rate("USD", "EUR")).isInstanceOf(RatesUnavailable.class);
}
@Test
void mapsUnknownCurrency() {
wm.stubFor(get(anyUrl()).willReturn(aResponse().withStatus(404)));
assertThatThrownBy(() -> rates.rate("USD", "XXX")).isInstanceOf(UnknownCurrency.class);
}
}
Pitfalls
- Only testing the happy path.
- Hitting real third-party sandboxes in every CI run.
- Timeout tests that take a long time (configure short timeouts in tests).
Variations
- Spring Cloud Contract for consumer-driven contracts.
MockRestServiceServerfor lightweight RestClient tests.
Further reading: WireMock: JUnit Jupiter
Reference: https://wiremock.org/docs/junit-jupiter/
More Spring Boot recipes
- Add correlation IDs to Spring Boot logs
- Add retries and a circuit breaker with Resilience4j
- Authenticate machine clients with API keys in Spring Security
- Cache service results in Redis with @Cacheable
- Consume Kafka messages with retries and a dead-letter topic
- Handle concurrent updates with optimistic locking
- Implement soft delete with Hibernate
- Make POST endpoints idempotent in Spring Boot