0
# Testing
1
2
Spring Boot WebFlux provides comprehensive testing support through WebTestClient and reactive testing utilities. This enables full integration testing of reactive web applications with support for mocking, assertions, and test-specific configurations.
3
4
## WebTestClient Interface
5
6
### Core WebTestClient
7
8
```java { .api }
9
public interface WebTestClient {
10
11
RequestHeadersUriSpec<?> get();
12
RequestBodyUriSpec post();
13
RequestBodyUriSpec put();
14
RequestBodyUriSpec patch();
15
RequestHeadersUriSpec<?> delete();
16
RequestHeadersUriSpec<?> options();
17
RequestHeadersUriSpec<?> head();
18
19
RequestBodyUriSpec method(HttpMethod method);
20
21
Builder mutate();
22
23
static Builder bindToServer();
24
static Builder bindToController(Object... controllers);
25
static Builder bindToApplicationContext(ApplicationContext applicationContext);
26
static Builder bindToRouterFunction(RouterFunction<?> routerFunction);
27
static Builder bindToWebHandler(WebHandler webHandler);
28
static Builder bindToWebServer(int port);
29
30
interface Builder {
31
Builder baseUrl(String baseUrl);
32
Builder defaultHeader(String header, String... values);
33
Builder defaultHeaders(Consumer<HttpHeaders> headersConsumer);
34
Builder defaultCookie(String cookie, String... values);
35
Builder defaultCookies(Consumer<MultiValueMap<String, String>> cookiesConsumer);
36
Builder filter(ExchangeFilterFunction filter);
37
Builder filters(Consumer<List<ExchangeFilterFunction>> filtersConsumer);
38
Builder clientConnector(ClientHttpConnector connector);
39
Builder codecs(Consumer<ClientCodecConfigurer> configurer);
40
Builder exchangeStrategies(ExchangeStrategies strategies);
41
Builder responseTimeout(Duration timeout);
42
43
WebTestClient build();
44
}
45
}
46
```
47
48
## Request Specification Interfaces
49
50
### RequestHeadersUriSpec
51
52
```java { .api }
53
public interface RequestHeadersUriSpec<S extends RequestHeadersSpec<S>> extends UriSpec<S> {
54
55
}
56
57
public interface RequestHeadersSpec<S extends RequestHeadersSpec<S>> {
58
S header(String headerName, String... headerValues);
59
S headers(Consumer<HttpHeaders> headersConsumer);
60
S accept(MediaType... acceptableMediaTypes);
61
S acceptCharset(Charset... acceptableCharsets);
62
S cookie(String name, String value);
63
S cookies(Consumer<MultiValueMap<String, String>> cookiesConsumer);
64
S ifModifiedSince(ZonedDateTime ifModifiedSince);
65
S ifNoneMatch(String... ifNoneMatches);
66
67
ResponseSpec exchange();
68
}
69
```
70
71
### RequestBodyUriSpec
72
73
```java { .api }
74
public interface RequestBodyUriSpec extends RequestBodySpec, RequestHeadersUriSpec<RequestBodySpec> {
75
76
}
77
78
public interface RequestBodySpec extends RequestHeadersSpec<RequestBodySpec> {
79
RequestHeadersSpec<?> contentLength(long contentLength);
80
RequestHeadersSpec<?> contentType(MediaType contentType);
81
82
RequestHeadersSpec<?> body(BodyInserter<?, ? super ClientHttpRequest> inserter);
83
<T> RequestHeadersSpec<?> body(Mono<T> body, Class<T> elementClass);
84
<T> RequestHeadersSpec<?> body(Mono<T> body, ParameterizedTypeReference<T> elementTypeRef);
85
<T> RequestHeadersSpec<?> body(Flux<T> body, Class<T> elementClass);
86
<T> RequestHeadersSpec<?> body(Flux<T> body, ParameterizedTypeReference<T> elementTypeRef);
87
RequestHeadersSpec<?> body(Object body);
88
89
RequestHeadersSpec<?> bodyValue(Object body);
90
}
91
```
92
93
## Response Specification and Assertions
94
95
### ResponseSpec
96
97
```java { .api }
98
public interface ResponseSpec {
99
100
StatusAssertions expectStatus();
101
HeaderAssertions expectHeader();
102
JsonPathAssertions expectJsonPath(String expression, Object... args);
103
XPathAssertions expectXPath(String expression, Object... args, Map<String, String> namespaces);
104
BodySpec<String, ?> expectBody(String content);
105
<T> BodySpec<T, ?> expectBody(Class<T> bodyType);
106
<T> BodySpec<T, ?> expectBody(ParameterizedTypeReference<T> bodyType);
107
<T> ListBodySpec<T> expectBodyList(Class<T> elementType);
108
<T> ListBodySpec<T> expectBodyList(ParameterizedTypeReference<T> elementType);
109
110
ResponseSpec expectBody();
111
112
<T> FluxExchangeResult<T> returnResult(Class<T> elementType);
113
<T> FluxExchangeResult<T> returnResult(ParameterizedTypeReference<T> elementTypeRef);
114
}
115
```
116
117
### StatusAssertions
118
119
```java { .api }
120
public interface StatusAssertions {
121
WebTestClient.ResponseSpec isOk();
122
WebTestClient.ResponseSpec isCreated();
123
WebTestClient.ResponseSpec isAccepted();
124
WebTestClient.ResponseSpec isNoContent();
125
WebTestClient.ResponseSpec isBadRequest();
126
WebTestClient.ResponseSpec isUnauthorized();
127
WebTestClient.ResponseSpec isForbidden();
128
WebTestClient.ResponseSpec isNotFound();
129
WebTestClient.ResponseSpec isEqualTo(HttpStatus status);
130
WebTestClient.ResponseSpec isEqualTo(int status);
131
WebTestClient.ResponseSpec is1xxInformational();
132
WebTestClient.ResponseSpec is2xxSuccessful();
133
WebTestClient.ResponseSpec is3xxRedirection();
134
WebTestClient.ResponseSpec is4xxClientError();
135
WebTestClient.ResponseSpec is5xxServerError();
136
}
137
```
138
139
## Testing Examples
140
141
### Controller Testing
142
143
```java
144
@ExtendWith(SpringExtension.class)
145
@WebFluxTest(UserController.class)
146
class UserControllerTest {
147
148
@Autowired
149
private WebTestClient webTestClient;
150
151
@MockBean
152
private UserService userService;
153
154
@Test
155
void shouldGetUser() {
156
// Given
157
User user = new User("1", "John Doe", "john@example.com");
158
when(userService.findById("1")).thenReturn(Mono.just(user));
159
160
// When & Then
161
webTestClient.get()
162
.uri("/api/users/{id}", "1")
163
.accept(MediaType.APPLICATION_JSON)
164
.exchange()
165
.expectStatus().isOk()
166
.expectHeader().contentType(MediaType.APPLICATION_JSON)
167
.expectBody(User.class)
168
.value(u -> {
169
assertThat(u.getId()).isEqualTo("1");
170
assertThat(u.getName()).isEqualTo("John Doe");
171
assertThat(u.getEmail()).isEqualTo("john@example.com");
172
});
173
}
174
175
@Test
176
void shouldCreateUser() {
177
// Given
178
User newUser = new User(null, "Jane Smith", "jane@example.com");
179
User savedUser = new User("2", "Jane Smith", "jane@example.com");
180
when(userService.save(any(User.class))).thenReturn(Mono.just(savedUser));
181
182
// When & Then
183
webTestClient.post()
184
.uri("/api/users")
185
.contentType(MediaType.APPLICATION_JSON)
186
.body(Mono.just(newUser), User.class)
187
.exchange()
188
.expectStatus().isCreated()
189
.expectBody(User.class)
190
.value(u -> assertThat(u.getId()).isEqualTo("2"));
191
}
192
193
@Test
194
void shouldGetAllUsers() {
195
// Given
196
List<User> users = Arrays.asList(
197
new User("1", "John Doe", "john@example.com"),
198
new User("2", "Jane Smith", "jane@example.com")
199
);
200
when(userService.findAll()).thenReturn(Flux.fromIterable(users));
201
202
// When & Then
203
webTestClient.get()
204
.uri("/api/users")
205
.accept(MediaType.APPLICATION_JSON)
206
.exchange()
207
.expectStatus().isOk()
208
.expectBodyList(User.class)
209
.hasSize(2)
210
.contains(users.get(0), users.get(1));
211
}
212
}
213
```
214
215
### Router Function Testing
216
217
```java
218
@ExtendWith(SpringExtension.class)
219
@WebFluxTest
220
class UserRouterTest {
221
222
@Autowired
223
private WebTestClient webTestClient;
224
225
@MockBean
226
private UserHandler userHandler;
227
228
@TestConfiguration
229
static class TestConfig {
230
@Bean
231
@Primary
232
RouterFunction<ServerResponse> testRoutes(UserHandler handler) {
233
return RouterFunctions.route()
234
.GET("/api/users/{id}", handler::getUser)
235
.GET("/api/users", handler::listUsers)
236
.POST("/api/users", handler::createUser)
237
.build();
238
}
239
}
240
241
@Test
242
void shouldRouteToGetUser() {
243
// Given
244
ServerRequest request = MockServerRequest.builder()
245
.method(HttpMethod.GET)
246
.uri(URI.create("/api/users/1"))
247
.pathVariable("id", "1")
248
.build();
249
250
User user = new User("1", "John Doe", "john@example.com");
251
when(userHandler.getUser(any(ServerRequest.class)))
252
.thenReturn(ServerResponse.ok().body(Mono.just(user), User.class));
253
254
// When & Then
255
webTestClient.get()
256
.uri("/api/users/{id}", "1")
257
.exchange()
258
.expectStatus().isOk();
259
}
260
}
261
```
262
263
### Integration Testing
264
265
```java
266
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
267
class UserIntegrationTest {
268
269
@Autowired
270
private WebTestClient webTestClient;
271
272
@Autowired
273
private UserRepository userRepository;
274
275
@BeforeEach
276
void setUp() {
277
userRepository.deleteAll().block();
278
}
279
280
@Test
281
void shouldPerformFullUserLifecycle() {
282
// Create user
283
User newUser = new User(null, "Integration Test", "integration@example.com");
284
285
User createdUser = webTestClient.post()
286
.uri("/api/users")
287
.contentType(MediaType.APPLICATION_JSON)
288
.body(Mono.just(newUser), User.class)
289
.exchange()
290
.expectStatus().isCreated()
291
.expectBody(User.class)
292
.returnResult()
293
.getResponseBody();
294
295
assertThat(createdUser.getId()).isNotNull();
296
297
// Get user
298
webTestClient.get()
299
.uri("/api/users/{id}", createdUser.getId())
300
.exchange()
301
.expectStatus().isOk()
302
.expectBody(User.class)
303
.value(u -> {
304
assertThat(u.getId()).isEqualTo(createdUser.getId());
305
assertThat(u.getName()).isEqualTo("Integration Test");
306
});
307
308
// Update user
309
User updatedUser = new User(createdUser.getId(), "Updated Name", "updated@example.com");
310
311
webTestClient.put()
312
.uri("/api/users/{id}", createdUser.getId())
313
.contentType(MediaType.APPLICATION_JSON)
314
.body(Mono.just(updatedUser), User.class)
315
.exchange()
316
.expectStatus().isOk()
317
.expectBody(User.class)
318
.value(u -> assertThat(u.getName()).isEqualTo("Updated Name"));
319
320
// Delete user
321
webTestClient.delete()
322
.uri("/api/users/{id}", createdUser.getId())
323
.exchange()
324
.expectStatus().isNoContent();
325
326
// Verify deletion
327
webTestClient.get()
328
.uri("/api/users/{id}", createdUser.getId())
329
.exchange()
330
.expectStatus().isNotFound();
331
}
332
}
333
```
334
335
### Custom WebTestClient Configuration
336
337
```java
338
@TestConfiguration
339
public class WebTestClientConfig {
340
341
@Bean
342
@Primary
343
public WebTestClient customWebTestClient() {
344
return WebTestClient.bindToServer()
345
.baseUrl("http://localhost:8080")
346
.responseTimeout(Duration.ofSeconds(30))
347
.filter(logRequest())
348
.filter(logResponse())
349
.defaultHeader("Accept", MediaType.APPLICATION_JSON_VALUE)
350
.defaultCookie("SESSION", "test-session")
351
.build();
352
}
353
354
private ExchangeFilterFunction logRequest() {
355
return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
356
System.out.println("Request: " + clientRequest.method() + " " + clientRequest.url());
357
clientRequest.headers().forEach((name, values) ->
358
values.forEach(value -> System.out.println(name + ": " + value))
359
);
360
return Mono.just(clientRequest);
361
});
362
}
363
364
private ExchangeFilterFunction logResponse() {
365
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
366
System.out.println("Response: " + clientResponse.statusCode());
367
return Mono.just(clientResponse);
368
});
369
}
370
}
371
```
372
373
### Testing Reactive Streams
374
375
```java
376
@Test
377
void shouldTestReactiveStream() {
378
Flux<String> source = Flux.just("foo", "bar", "baz");
379
380
StepVerifier.create(source)
381
.expectNext("foo")
382
.expectNext("bar")
383
.expectNext("baz")
384
.verifyComplete();
385
}
386
387
@Test
388
void shouldTestErrorHandling() {
389
Flux<String> source = Flux.just("foo", "bar")
390
.concatWith(Flux.error(new RuntimeException("boom")));
391
392
StepVerifier.create(source)
393
.expectNext("foo")
394
.expectNext("bar")
395
.expectError(RuntimeException.class)
396
.verify();
397
}
398
399
@Test
400
void shouldTestWithVirtualTime() {
401
StepVerifier.withVirtualTime(() ->
402
Flux.interval(Duration.ofHours(4)).take(2))
403
.expectSubscription()
404
.expectNoEvent(Duration.ofHours(4))
405
.expectNext(0L)
406
.thenAwait(Duration.ofHours(4))
407
.expectNext(1L)
408
.verifyComplete();
409
}
410
```
411
412
### Test Slices
413
414
```java
415
// Test only WebFlux layer
416
@WebFluxTest(UserController.class)
417
class WebFluxLayerTest {
418
// Only WebFlux components are loaded
419
}
420
421
// Test with custom auto-configuration
422
@WebFluxTest(
423
controllers = UserController.class,
424
excludeAutoConfiguration = SecurityAutoConfiguration.class
425
)
426
class CustomWebFluxTest {
427
// WebFlux with custom configuration
428
}
429
430
// Test specific components
431
@TestConfiguration
432
static class TestConfig {
433
@Bean
434
@Primary
435
public UserService mockUserService() {
436
return Mockito.mock(UserService.class);
437
}
438
}
439
```