Skip to main content

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

· 5 min read
Evgenii Frolikov
Senior Java Architect | Expert in High-Load Systems & JVM Internals

Feign Client Integration Testing: WireMock vs BitDive Trace 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:

  1. Mock the Java interface using @MockBean
  2. Stub the HTTP server with WireMock
  3. 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 @RequestHeader annotations on the Feign interface.
  • Typos in the @GetMapping path.
  • Custom ErrorDecoder failures. 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_case but your Feign configuration defaults to camelCase.
  • RequestInterceptor bugs, 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.

Approach 3: The deterministic path (runtime replay)

The problem with both @MockBean and WireMock is that you are guessing the behavior of the external system.

What if you didn't have to write the stub?

This is where the Autonomous Verification Layer comes in. Tools like BitDive capture real HTTP exchanges from a running environment and automatically build deterministic tests.

Instead of writing a WireMock stub, you let the application make a real call to the Payment API once. BitDive records the exact request headers, body, and the response.

How it looks in BitDive:

@Test
@BitDiveReplay(scenarioId = "order-success-payment-v1")
void processOrder_replay() {
// Full Spring Context boots.
// HTTP call to OrderService is triggered.
// BitDive intercepts the Feign call and injects the recorded response.
}

The pros

  • There is no mock maintenance. The HTTP stub is generated from the recorded execution trace.
  • It has high fidelity. Because the trace contains the exact bytes returned by the real server, you test your Jackson configuration against reality.
  • It is safer for AI-native teams. When AI coding agents like Cursor refactor Feign clients, they can compare traces to ensure they didn't drop a header or alter the serialization contract. See runtime context for AI.

The flaws

  • It requires a running environment to capture the initial recording.

Summary

The days of writing hundreds of lines of stubFor(...) configuration are ending.

  1. Use @MockBean for pure domain-logic unit tests.
  2. Use WireMock for strict consumer-driven contract testing.
  3. Use runtime replay for most integration tests. It gives you the speed of a mock with the certainty of real production data.

Stop guessing what the external API returns. Record it and verify it.