skip to content

What does @RestClientTest do, and how do you assert against outgoing HTTP calls with MockRestServiceServer?

level: middleimportance: must knowfreq 55%

answer

  1. Client-side slice — outgoing HTTP
  2. MockRestServiceServer intercepts the request factory
  3. expect(requestTo/method).andRespond(withSuccess)
  4. server.verify() checks all expectations hit
  5. Must build from injected RestTemplateBuilder/RestClient.Builder

basics

~20 s

@RestClientTest is a slice for testing a component that calls a remote API via RestTemplate or RestClient. It auto-configures a MockRestServiceServer that intercepts outgoing requests, lets you set up expected requests and canned responses, and verifies they were called.

solid answer

~40 s

@RestClientTest tests the *client side* — a class that consumes an external HTTP API through `RestTemplate`, `RestClient`, or `RestTemplateBuilder`/`RestClient.Builder`. You point it at your caller with `@RestClientTest(MyApiClient.class)`. It auto-configures a `MockRestServiceServer` and binds it to the client's request factory, so no real network call is made. In the test you record expectations: `server.expect(requestTo("/users/1")).andExpect(method(GET)).andRespond(withSuccess(json, APPLICATION_JSON))`. Your client then issues the call and receives the canned response. Finally `server.verify()` (implicit at teardown, or explicit) asserts every expected request occurred the expected number of times, in order by default. It also auto-configures Jackson so serialization matches production. Use it to test request URL/method/headers/body construction and response parsing/error handling in isolation.

code

java · 22 lines
java
@RestClientTest(RemoteUserClient.class)
class RemoteUserClientTest {

    @Autowired RemoteUserClient client;
    @Autowired MockRestServiceServer server;

    @Test
    void fetchesUser() {
        server.expect(requestTo("/users/1"))
              .andExpect(method(HttpMethod.GET))
              .andExpect(header("Accept", "application/json"))
              .andRespond(withSuccess("{\"id\":1,\"name\":\"Ada\"}",
                                      MediaType.APPLICATION_JSON));

        User u = client.getUser(1L);

        assertThat(u.name()).isEqualTo("Ada");
        server.verify(); // all expectations satisfied
    }
}
// Client must build from the injected builder so the mock attaches:
// this.restClient = builder.baseUrl("...").build();

go deeper

for a junior

Know it tests a class that calls an external API and lets you stub responses.

for a middle

Fluently set up expect/andRespond, verify, and know the builder-binding requirement.

for a senior

Discuss ExpectedCount, ordering, error responders, and why the request factory binding matters.

for a principal

Weigh MockRestServiceServer vs WireMock/contract tests for coupling, protocol fidelity, and consumer-driven contracts.

## Purpose `@RestClientTest` (from `org.springframework.boot.test.autoconfigure.web.client`) is a Spring Boot **test slice for HTTP client code** — the component in *your* app that reaches out to a *remote* service. Where `@WebFluxTest`/`@WebMvcTest` test the server side (incoming requests), `@RestClientTest` tests the **outgoing** side. ## What it loads A minimal context with: - Your client class (name it: `@RestClientTest(RemoteUserClient.class)`). - A **`RestTemplateBuilder`** and/or **`RestClient.Builder`** so your client is built the same way as in production. - Jackson JSON auto-configuration (so (de)serialization matches prod). - A **`MockRestServiceServer`** wired to intercept the client's requests. It does **not** load your services, repositories, or web controllers. ## MockRestServiceServer — the core tool `MockRestServiceServer` (from `org.springframework.test.web.client`) replaces the client's real `ClientHttpRequestFactory` with a mock that **matches requests against recorded expectations and returns stubbed responses** — no socket is opened. Workflow: 1. **Record expectations** (before the call): `server.expect(requestTo("/api/users/1")).andExpect(method(HttpMethod.GET)).andRespond(withSuccess("{...}", MediaType.APPLICATION_JSON));` Matchers come from `MockRestRequestMatchers` (`requestTo`, `method`, `header`, `content().json(...)`, `jsonPath(...)`, `queryParam`). Responders come from `MockRestResponseCreators` (`withSuccess`, `withStatus`, `withServerError`, `withResourceNotFound`, `withException`, `withTooManyRequests`). 2. **Exercise the client** — call the method under test; it issues the HTTP request, which the mock matches and answers. 3. **Verify** — `server.verify()` asserts all expectations were met (every recorded request happened, the right number of times). Boot calls verify automatically at test teardown too, but explicit `verify()` is clearer. ## Binding to RestTemplate vs RestClient Critical gotcha: `MockRestServiceServer` intercepts by **replacing the request factory of the specific client instance it's bound to**. In `@RestClientTest` this binding is automatic *as long as your client is built from the auto-configured `RestTemplateBuilder` / `RestClient.Builder`*. If you `new RestTemplate()` yourself inside the client (bypassing the builder), the mock is **not** attached and real calls leak out. Always build from the injected builder. ## Ordering and counts By default expectations must occur **in the exact order recorded**. To relax, bind with an `unordered` request order (`MockRestServiceServer.bindTo(...).ignoreExpectOrder(true)`), though under `@RestClientTest` the default ordered server is provided. Expected count is controlled by `ExpectedCount` (e.g. `expect(times(2), requestTo(...))`, `once()`, `manyTimes()`, `min()`, `max()`, `never()`). ## Response bodies Boot injects a `@Value`-style helper? No — commonly you load fixtures via the auto-configured `ObjectMapper` or use `withSuccess(jsonString, MediaType.APPLICATION_JSON)`. You can also use Spring Boot's `@JsonTest`-style resources, but plainly a JSON string works. ## Error-path testing Use responders like `withStatus(HttpStatus.INTERNAL_SERVER_ERROR)` or `withServerError()` to assert your client maps failures correctly (throws, retries, returns fallback). ## Gotchas - Bypassing the builder → mock not attached. - Forgetting to record an expectation → `AssertionError: No further requests expected` when the client calls out. - Recording an expectation but never calling it → `verify()` fails with unsatisfied expectation. - Reuse across tests: create/verify per test; the server is reset per test method. ## When to use Use `@RestClientTest` to test URL/method/header/body construction and response/error parsing of an HTTP client, without a real backend or WireMock.

  • Why might your MockRestServiceServer expectations never fire, letting a real call go out?
    Because the client instantiated its RestTemplate/RestClient directly (e.g. new RestTemplate()) instead of using the auto-configured RestTemplateBuilder/RestClient.Builder. The mock only intercepts the client instance whose request factory it replaced, so build from the injected builder.
  • What happens if you record an expectation but the code never makes that call?
    server.verify() fails with an unsatisfied-expectation assertion, because MockRestServiceServer requires every recorded expectation to be met the expected number of times.

saying these in an interview costs you the question

  • Thinking @RestClientTest tests incoming requests / your controllers
  • Using new RestTemplate() in the client and expecting the mock to intercept
  • Believing MockRestServiceServer makes real network calls
  • Forgetting verify() and assuming expectations are optional

context