skip to content

In MockRestServiceServer, how do you control expected call counts and request ordering, and test retries or multiple calls?

level: seniorimportance: should knowfreq 32%

answer

  1. ExpectedCount: once/times/min/max/between/manyTimes/never
  2. expect(count, matcher) overload
  3. Ordered by default — request N matches expectation N
  4. ignoreExpectOrder(true) → unordered manager
  5. Retry test: times(3) absorbs 3 calls; or two once() responders

basics

~20 s

Use ExpectedCount in server.expect(...) — like once(), times(n), min(n), max(n), manyTimes(), never() — to say how many times a request should occur. By default expectations must happen in the order recorded; you can build an unordered server (ignoreExpectOrder) so requests match in any order, which is useful for retries or concurrent calls.

solid answer

~40 s

`server.expect(...)` takes an optional `ExpectedCount` as its first argument: `expect(ExpectedCount.times(3), requestTo(url))`, plus `once()`, `manyTimes()`, `min(n)`, `max(n)`, `between(a,b)`, `never()`. This controls how many matching requests the single expectation absorbs — ideal for testing a retry policy that hits the same URL N times. Ordering: by default `MockRestServiceServer` enforces **strict recorded order** — request 1 must match expectation 1. When calls can arrive in any order (retries with jitter, parallel calls, unpredictable sequencing) build the server unordered: `MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build()`, so any incoming request matches any still-satisfiable expectation. `verify()` then confirms all counts were met. For different responses across retries you record multiple expectations (first fails, second succeeds) — with unordered/count semantics chosen to model the retry behavior.

code

java · 14 lines
java
// Test a client that retries a GET up to 3 times on 5xx, then falls back.
MockRestServiceServer server = MockRestServiceServer
        .bindTo(restTemplate)
        .ignoreExpectOrder(true)   // retries may not be strictly ordered
        .build();

server.expect(ExpectedCount.times(3), requestTo("/quote"))
      .andExpect(method(HttpMethod.GET))
      .andRespond(withStatus(HttpStatus.SERVICE_UNAVAILABLE));

String quote = quoteClient.getQuoteOrFallback();

assertThat(quote).isEqualTo("N/A"); // fallback after 3 failures
server.verify(); // confirms exactly 3 calls were made

go deeper

for a junior

Know expect() defaults to exactly one call and verify() checks it happened.

for a middle

Use ExpectedCount variants and know ordering is strict by default.

for a senior

Model retries/fallbacks/caching with counts + ordered/unordered managers correctly.

for a principal

Standardize how the team asserts downstream call semantics (idempotency, retry ceilings, cache-hit no-call) and when MockRestServiceServer is insufficient vs contract/integration tests.

## ExpectedCount — how many times `MockRestServiceServer.expect(...)` has two overloads: - `expect(RequestMatcher)` — shorthand for **exactly once**. - `expect(ExpectedCount count, RequestMatcher)` — explicit count. `ExpectedCount` (in `org.springframework.test.web.client`) factory methods: - `once()` — exactly 1. - `manyTimes()` — 1 or more, unbounded. - `times(n)` — exactly n. - `min(n)` — at least n. - `max(n)` — at most n. - `between(min, max)` — inclusive range. - `never()` — the request must **not** occur. A single expectation with `times(3)` **absorbs three matching requests**. This is how you test a **retry policy**: if your client retries a GET up to 3 times on 5xx, record `expect(times(3), requestTo(url)).andRespond(withServerError())` and assert the client ultimately fails/falls back — or model the last attempt succeeding with separate expectations. ## Ordering — strict by default By default `MockRestServiceServer` matches requests **in the exact order the expectations were recorded** (ordered/`RequestExpectationManager` = `SimpleRequestExpectationManager` semantics). Request #1 from the client must satisfy expectation #1, etc. If the second real request doesn't match the second expectation, the test fails immediately. ### Unordered matching When request order is non-deterministic — retries with backoff, parallel calls, or you simply don't care — create an **unordered** server: ```java MockRestServiceServer server = MockRestServiceServer .bindTo(restTemplate) .ignoreExpectOrder(true) .build(); ``` `ignoreExpectOrder(true)` uses `UnorderedRequestExpectationManager`: each incoming request matches **any** recorded expectation that can still be satisfied (respecting its count). Note: under `@RestClientTest` the auto-configured server is ordered; when you need unordered you often bind manually (still against the auto-configured `RestTemplate`/`RestClient` request factory) or use `@RestClientTest` and re-bind. ## Modeling retries with different responses To test "fails once, then succeeds": ```java server.expect(once(), requestTo(url)).andRespond(withServerError()); server.expect(once(), requestTo(url)).andRespond(withSuccess(body, JSON)); ``` With ordered semantics the first call gets the 500, the retry gets the 200. Then `verify()` ensures both happened. ## Resetting `server.reset()` clears expectations and recorded requests (rarely needed since the slice resets per test). `verify()` asserts every expectation met its minimum count. ## Common gotchas - **`times(n)` under-called**: if the client calls fewer than n times, `verify()` fails — good for asserting exact retry counts. - **Ordered vs unordered mismatch**: retries with jitter can violate strict order; use `ignoreExpectOrder(true)`. - **`manyTimes()` never verifies an upper bound**: it accepts 1..∞, so it won't catch an over-eager client. Use `times`/`max` when you need a ceiling. - **`never()`** is powerful for asserting a code path did NOT call a downstream (e.g., cache hit). - **Response reuse**: each expectation's responder fires per matched request; with `times(3)` the same responder answers all three. ## When to use Reach for `ExpectedCount` + ordering control whenever behavior depends on *how many* and *in what order* your client calls a downstream: retry/backoff policies, idempotency, caching (assert `never()` on a hit), fan-out calls, and circuit-breaker fallbacks.

  • How would you assert that a cache hit means NO downstream HTTP call is made?
    Record the expectation with ExpectedCount.never(): server.expect(never(), requestTo(url)). If the client calls out despite the cache, verify() (or the immediate match failure) fails the test.
  • Your client retries with random jitter and the strict-order server fails intermittently. Fix?
    Build the server with .ignoreExpectOrder(true) so it uses the UnorderedRequestExpectationManager; any incoming request matches any still-satisfiable expectation, removing order sensitivity.

saying these in an interview costs you the question

  • Thinking expect() can only match a single request
  • Assuming request order never matters (it's strict by default)
  • Using manyTimes() when you need to assert an exact/maximum count
  • Believing you can't test retries with MockRestServiceServer

context