Feign client integration testing in Spring Boot: WireMock, MockBean, or runtime replay?

TL;DR: Testing Spring Cloud OpenFeign clients is difficult to get right. Mocking the Java interface misses serialization and interceptor logic. WireMock tests the full HTTP stack but requires constant manual updates to JSON stubs. A more reliable alternative is runtime replay. You capture real HTTP exchanges from production and virtualize the Feign boundaries during your Spring Boot integration tests.
The Feign testing dilemma
Spring Cloud OpenFeign makes REST APIs feel like local methods. It handles URLs, encoding, decoding, headers, and retries.
Because it hides the HTTP complexity, developers often assume testing the interface is enough. This often leads to bugs where the client passes tests in CI but fails in production because a header was missing or the JSON format was slightly different.
When writing an integration test for a service that uses Feign, you have three options to handle that outbound dependency:
- Mock the Java interface using
@MockBean - Stub the HTTP server with
WireMock - Use trace-based testing (runtime replay)
Approach 1: The fast path (@MockBean)
The most common approach in Spring Boot is to mock the Feign interface using Mockito's @MockBean.
@SpringBootTest
class OrderServiceIntegrationTest {
@MockBean
private PaymentClient paymentClient;
@Test
void processOrder_CallsPaymentClient() {
PaymentResponse response = new PaymentResponse("SUCCESS", "tx-123");
when(paymentClient.charge(any())).thenReturn(response);
orderService.processOrder(new OrderRequest());
verify(paymentClient).charge(any());
}
}
The pros
- It is fast. There are no network calls, no Jackson serialization, and no HTTP servers.
- It is easy to write. You instantiate standard Java objects.
The flaws
You are testing the mock, not the Feign configuration. @MockBean removes the entire HTTP stack. This approach won't catch:
- Missing
@RequestHeaderannotations on the Feign interface. - Typos in the
@GetMappingpath. - Custom
ErrorDecoderfailures. For example, if the API returns a 400 error, does your service handle the custom exception? - Jackson serialization drift. This happens if the remote API expects
snake_casebut your Feign configuration defaults tocamelCase. RequestInterceptorbugs, such as failing to attach OAuth2 tokens to outbound requests.
Verdict: Good for narrow business logic unit tests, but insufficient for integration tests.
Approach 2: The realistic path (WireMock)
To test the actual HTTP boundary, you need to spin up a mock server. WireMock intercepts the real HTTP call generated by Feign.
@SpringBootTest
@AutoConfigureWireMock(port = 8081)
class OrderServiceWireMockTest {
@Test
void processOrder_WithWireMock() {
stubFor(post(urlEqualTo("/api/payments"))
.withHeader("Authorization", containing("Bearer"))
.willReturn(aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody("{ \"status\": \"SUCCESS\", \"transactionId\": \"tx-123\" }")));
orderService.processOrder(new OrderRequest());
}
}
The pros
- It is realistic. The test forces Spring Boot to serialize the request, pass it through Feign interceptors, and receive the JSON.
- It catches configuration errors. It proves your timeouts, URL bindings, and Jackson modules are correct.
The flaws
- It is hard to maintain. You have to manually maintain JSON strings or files. When the remote API changes its payload, your hardcoded string doesn't update.
- It is complex. Replicating paginated responses or deeply nested JSON takes a lot of time.
Verdict: Highly realistic, but creates a significant maintenance burden.
