How does Spring REST Docs work with WebTestClient for a reactive/WebFlux application, and how does it differ from the MockMvc integration?
answer
- spring-restdocs-webtestclient module
- WebTestClientRestDocumentation.documentationConfiguration = filter
- .consumeWith(document(...)) not .andDo(...)
- set baseUrl / modifyUris (no servlet Host)
- same descriptors, snippets, strict validation
basics
~10 sUse the spring-restdocs-webtestclient module. Configure WebTestClient with WebTestClientRestDocumentation.documentationConfiguration, then call .consumeWith(WebTestClientRestDocumentation.document("id", ...)) on the response. Same snippets and descriptors as MockMvc, just a different entry-point class.
solid answer
~30 sFor WebFlux (or any WebTestClient-based test) you swap the module and the documentation class. Add spring-restdocs-webtestclient instead of spring-restdocs-mockmvc. You build the client with WebTestClient.bindToApplicationContext(context).configureClient().filter(WebTestClientRestDocumentation.documentationConfiguration(restDocumentation)).build(), or with @AutoConfigureRestDocs + @AutoConfigureWebTestClient in a Boot test which wires it for you. Documentation attaches at the assertion terminal: client.get().uri("/users/{id}", 1).exchange().expectStatus().isOk().expectBody().consumeWith(WebTestClientRestDocumentation.document("users-get", responseFields(...))). The snippet types, field descriptors, preprocessors and strict validation are identical to MockMvc — only the harness differs. A WebTestClient nuance: you should set a base URI (e.g. .baseUrl("https://api.example.com")) or use modifyUris, since there's no servlet Host to infer from.
code
java · 31 linesimport static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.document;
import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.documentationConfiguration;
import static org.springframework.restdocs.payload.PayloadDocumentation.*;
@ExtendWith(RestDocumentationExtension.class)
@SpringBootTest
class UserHandlerDocsTest {
WebTestClient client;
@BeforeEach
void setUp(ApplicationContext context, RestDocumentationContextProvider restDocs) {
this.client = WebTestClient.bindToApplicationContext(context)
.configureClient()
.baseUrl("https://api.example.com")
.filter(documentationConfiguration(restDocs))
.build();
}
@Test
void getUser() {
client.get().uri("/users/{id}", 1)
.exchange()
.expectStatus().isOk()
.expectBody()
.consumeWith(document("users-get",
responseFields(
fieldWithPath("id").description("Unique id"),
fieldWithPath("name").description("Display name"))));
}
}go deeper
Know WebTestClient uses a different module and .consumeWith(document(...)).
Wire the documentationConfiguration filter and reuse the same responseFields/preprocessors as MockMvc.
Explain the baseUrl/modifyUris need, bindToApplicationContext vs bindToServer, and that WebTestClient covers both WebFlux and MVC.
Choose the harness per stack (WebFlux -> WebTestClient) and keep one descriptor/preprocessor convention across MockMvc and WebTestClient tests.
**Three supported test harnesses.** Spring REST Docs is harness-agnostic: the same snippet engine is fed by one of three integration modules — `spring-restdocs-mockmvc` (Servlet MVC via MockMvc), `spring-restdocs-webtestclient` (reactive WebFlux and also MVC via `WebTestClient`), and `spring-restdocs-restassured` (black-box against a running server). This leaf focuses on MockMvc and WebTestClient. **WebTestClient setup (manual).** ```java @ExtendWith(RestDocumentationExtension.class) class Docs { WebTestClient client; @BeforeEach void setup(ApplicationContext ctx, RestDocumentationContextProvider rd) { this.client = WebTestClient.bindToApplicationContext(ctx) .configureClient() .baseUrl("https://api.example.com") .filter(WebTestClientRestDocumentation.documentationConfiguration(rd)) .build(); } } ``` The `documentationConfiguration(...)` is an `ExchangeFilterFunction` that captures each exchange. `WebTestClientRestDocumentation` lives in `org.springframework.restdocs.webtestclient`. **Boot slice.** `@AutoConfigureRestDocs` combined with `@AutoConfigureWebTestClient` (or a full `@SpringBootTest(webEnvironment = RANDOM_PORT)` with an injected `WebTestClient`) auto-configures the filter, so you just inject `WebTestClient` and call `document(...)`. **Where documentation attaches.** Unlike MockMvc's `.andDo(document(...))` `ResultHandler`, WebTestClient uses the body-consumer terminal: ```java client.get().uri("/users/{id}", 1) .exchange() .expectStatus().isOk() .expectBody() .consumeWith(document("users-get", responseFields(fieldWithPath("id").description("id")))); ``` `document(...)` here returns a `Consumer<EntityExchangeResult<byte[]>>`. Everything downstream — `responseFields`, `requestFields`, `pathParameters`, `preprocessRequest/Response`, strict validation — is the *same* descriptor API imported from the same `payload`/`request`/`operation.preprocess` packages. **Key differences vs MockMvc.** - Different module + different documentation class (`WebTestClientRestDocumentation` vs `MockMvcRestDocumentation`). - Attachment point: `.consumeWith(document(...))` on the body vs `.andDo(document(...))` on the result. - Path templates: use `.uri("/users/{id}", 1)` so `pathParameters` can resolve the template. - **Base URI**: WebTestClient has no servlet request Host, so set `.baseUrl(...)` (or use `modifyUris()` preprocessor / `@AutoConfigureRestDocs` URI attributes) or documented curl/http-request snippets show a placeholder host. - WebTestClient can drive *both* WebFlux and MVC apps; MockMvc is Servlet-only. **Gotchas.** - Forgetting the `filter(documentationConfiguration(...))` means `document(...)` throws because no context was captured. - With `bindToApplicationContext`/`bindToController` the calls are in-process (no network) — like MockMvc; with `bindToServer` it's a real HTTP client (closer to REST Assured). - Reactive streaming responses are captured as the fully materialized body for documentation. **When to use.** Use the WebTestClient integration for WebFlux endpoints or when your team already standardizes on WebTestClient for controller tests, so docs come from the same tests you already write.
- What is the attachment point for document() with WebTestClient, and how does it differ from MockMvc?With WebTestClient you call .consumeWith(document(...)) on expectBody() — document returns a Consumer of the exchange result. MockMvc uses .andDo(document(...)) as a ResultHandler. The descriptor/snippet APIs are otherwise identical.
- Why do you often need to set a baseUrl or modifyUris with WebTestClient but rarely with MockMvc?MockMvc synthesizes a servlet request with localhost, giving REST Docs a Host to render. WebTestClient (bound to context) has no servlet Host, so the documented request URI needs an explicit baseUrl or a modifyUris preprocessor to look production-like.
saying these in an interview costs you the question
- Using MockMvcRestDocumentation.document() with WebTestClient (wrong module/class)
- Forgetting the documentationConfiguration filter, so nothing is captured
- Assuming field descriptors/strict rules differ between the two harnesses (they don't)