As a tech lead, how do you decide between mock-server and running-server WebTestClient tests, and what pitfalls (timeouts, fidelity, security) do you watch for?
answer
- pyramid: many mock-server, few running-server
- RANDOM_PORT for transport/filter/CORS/SSE fidelity
- 5s default timeout → set explicitly
- bindToController drifts from prod config
- security-test configurers, not hand-set headers
basics
~10 sUse fast mock-server tests (bindToController/context, webEnvironment MOCK) for most controller coverage; reserve slower running-server tests (RANDOM_PORT) for transport, filter-ordering, and true end-to-end concerns. Watch response timeouts, hidden config gaps, and security-context wiring.
solid answer
~50 sI treat it as a test-pyramid decision. The bulk of endpoint tests run against a mock server (bindToApplicationContext or Boot's MOCK environment, or bindToController for isolated cases) because they're fast, deterministic, and exercise mapping, validation, serialization, and advice. I add a thin layer of running-server tests (@SpringBootTest RANDOM_PORT) where fidelity matters: servlet/Netty transport, real filter chains and ordering, CORS, chunked/streaming behavior, and full security filter enforcement. Pitfalls I guard against: the default 5s response timeout hiding as flaky failures on slow paths (set it explicitly); bindToController silently missing @ControllerAdvice/converters and diverging from prod; mock-server tests passing while a real container behaves differently; security tests needing spring-security-test configurers (mockUser/csrf) rather than hand-set headers; and streaming endpoints requiring returnResult + StepVerifier to avoid hangs. I also standardize base config via a shared builder/mutate() so timeouts, auth, and codecs are consistent.
code
java · 18 lines// Shared, explicitly-timed, auth-aware client for a running-server tier
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureWebTestClient
class EndToEndApiTest {
@Autowired WebTestClient base;
@Test
void secureEndpointOverRealHttp() {
base.mutate()
.responseTimeout(Duration.ofSeconds(15))
.build()
.mutateWith(mockUser("admin").roles("ADMIN")) // spring-security-test
.get().uri("/admin/report")
.exchange()
.expectStatus().isOk();
}
}go deeper
Understand there are fast (mock) and slow (real server) test options.
Pick the right mode per test and know the default timeout can bite.
Justify a mock-heavy pyramid with a thin real-server tier for transport/security/streaming fidelity.
Own the org's endpoint-testing strategy: tiering, shared config, security wiring, and closing fidelity gaps.
## Framing: it's a test-pyramid and fidelity decision WebTestClient can run in two fundamentally different modes, and choosing per test tier is an architecture call. ### Mock-server mode (default for volume) `bindToApplicationContext`, `bindToController`, `bindToRouterFunction`, or Boot's `webEnvironment = MOCK`. Requests are handled **in-process** — no socket. - **Pros:** milliseconds per test, deterministic, no port contention, parallel-friendly. Still exercises request mapping, argument resolution, `@Valid` validation, `HttpMessageConverter` serialization, and (with a context) `@ControllerAdvice`, filters, and security. - **Use for:** the majority of controller/endpoint behavior — happy paths, validation errors, error mapping, content negotiation, authorization rules (with security context wired). ### Running-server mode (thin, high-fidelity layer) `@SpringBootTest(webEnvironment = RANDOM_PORT)` + `@AutoConfigureWebTestClient`, or explicit `bindToServer().baseUrl(...)`. Real embedded container, real sockets. - **Pros:** exercises the **actual transport** — Tomcat/Netty specifics, real filter chain + ordering, connection/keep-alive, chunked transfer, compression, true header casing, CORS preflight, and the full security filter chain as deployed. - **Cons:** slower (container startup), port management, more moving parts. - **Use for:** end-to-end smoke tests, streaming/SSE over real HTTP, filter-ordering regressions, anything that only manifests over the wire. ## Decision heuristics 1. **Default to mock-server** for coverage volume; keep the suite fast. 2. **Escalate to running-server** only when a behavior depends on the real container/transport or you're validating the deployment as a whole. 3. **Prefer `bindToApplicationContext` over `bindToController`** when you need production-like config, because standalone controller binding omits context-driven pieces (advice, converters, filters, security) and can pass while prod differs. ## Key pitfalls ### Response timeout WebTestClient blocks with a **default 5-second response timeout**. Slow endpoints (cold caches, external calls) fail intermittently and look like flakiness. Set it explicitly per suite: `client.mutate().responseTimeout(Duration.ofSeconds(15)).build()`. ### Fidelity gaps in mock mode Mock-server tests **bypass the real container**, so bugs in filter ordering, transport, header handling, or CORS preflight may not surface. Don't let a green mock suite create false confidence about deployment concerns — keep a running-server layer. ### bindToController config drift Because it loads no context, missing `@ControllerAdvice`, validators, or codecs change behavior silently. Register them explicitly or use the context-bound variant. ### Security wiring With Spring Security, use `spring-security-test` configurers via `mutateWith(SecurityMockServerConfigurers.mockUser())` / `csrf()` (reactive) or the MVC equivalents, rather than hand-crafting Authorization headers — this correctly populates the security context and CSRF tokens. In mock mode ensure the security filter/context is actually in the loaded config; in running-server mode the real chain enforces it. ### Streaming endpoints Inline `expectBodyList` on SSE/unbounded streams **hangs until timeout**. Use `returnResult(Class)` → `Flux` + `StepVerifier` with explicit demand/cancellation. ### Consistency / duplication Standardize a shared base client (via a builder or `mutate()`) for timeouts, default auth, base headers, and codecs so tests don't drift. Consider a test fixture/`@TestConfiguration` exposing a preconfigured WebTestClient. ### Parallelism and ports Running-server tests consume ports and container startup time; be deliberate about how many you run and use `RANDOM_PORT` to avoid clashes. ## Bottom line Most endpoint verification belongs in fast mock-server tests; reserve the slower running-server tier for genuine transport/filter/security/streaming fidelity — and control timeouts, config completeness, and security wiring explicitly.
- Your mock-server suite is green but a filter-ordering bug reached production. What testing gap caused it and how do you close it?Mock-server mode bypasses the real container and filter chain, so ordering bugs don't surface. Add a running-server tier (@SpringBootTest RANDOM_PORT) that exercises the real filter chain, and cover the specific ordering assertion there.
- How do you authenticate WebTestClient requests without leaking real credentials into tests?Use spring-security-test configurers via mutateWith(mockUser()/authentication()) (and csrf() where needed), which populate the security context directly instead of real tokens or hand-set Authorization headers.
saying these in an interview costs you the question
- Running everything against a real server, making the suite slow and flaky.
- Assuming mock-server tests give full confidence in transport, CORS, and filter ordering.
- Ignoring the 5s default response timeout as a source of flakiness.
- Hand-crafting auth headers instead of using spring-security-test configurers.