Skip to content

Commit fbf2e1e

Browse files
committed
Documentation for WebTestClient
Issue: SPR-16009
1 parent 2ecd5a4 commit fbf2e1e

File tree

3 files changed

+297
-9
lines changed

3 files changed

+297
-9
lines changed
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
[[webtestclient]]
2+
= WebTestClient
3+
4+
`WebTestClient` is a non-blocking, reactive client for testing web servers. It uses
5+
the reactive <<web-reactive.adoc#webflux-webclient, WebClient>> internally to perform
6+
requests and provides a fluent API to verify responses. The `WebTestClient` can connect
7+
to any server over an HTTP connection. It can also bind directly to WebFlux applications
8+
with <<testing.adoc#mock-objects-web-reactive,mock request and response>> objects,
9+
without the need for an HTTP server.
10+
11+
12+
13+
[[webtestclient-setup]]
14+
== Setup
15+
16+
To create a `WebTestClient` you must choose one of several server setup options.
17+
Effectively you either configure a WebFlux application to bind to, or use absolute URLs
18+
to connect to a running server.
19+
20+
21+
[[webtestclient-controller-config]]
22+
=== Bind to controller
23+
24+
Use this server setup to test one `@Controller` at a time:
25+
26+
[source,java,intent=0]
27+
[subs="verbatim,quotes"]
28+
----
29+
client = WebTestClient.bindToController(new TestController()).build();
30+
----
31+
32+
The above loads the <<web-reactive.adoc#webflux-config,WebFlux Java config>> and
33+
registers the given controller. The resulting WebFlux application will be tested
34+
without an HTTP server using mock request and response objects. There are more methods
35+
on the builder to customize the default WebFlux Java config.
36+
37+
38+
[[webtestclient-fn-config]]
39+
=== Bind to RouterFunction
40+
41+
Use this option to set up a server from a
42+
<<web-reactive.adoc#webflux-fn,RouterFunction>>:
43+
44+
[source,java,intent=0]
45+
[subs="verbatim,quotes"]
46+
----
47+
RouterFunction<?> route = ...
48+
client = WebTestClient.bindToRouterFunction(route).build();
49+
----
50+
51+
Internally the provided configuration is passed to `RouterFunctions.toWebHandler`.
52+
The resulting WebFlux application will be tested without an HTTP server using mock
53+
request and response objects
54+
55+
56+
[[webtestclient-context-config]]
57+
=== Bind to ApplicationContext
58+
59+
Use this option to setup a server from the Spring configuration of your application, or
60+
some subset of it:
61+
62+
[source,java,intent=0]
63+
[subs="verbatim,quotes"]
64+
----
65+
@RunWith(SpringRunner.class)
66+
@ContextConfiguration(classes = WebConfig.class) // <1>
67+
public class MyTests {
68+
69+
@Autowired
70+
private ApplicationContext context; // <2>
71+
72+
private WebTestClient client;
73+
74+
@Before
75+
public void setUp() {
76+
client = WebTestClient.bindToApplicationContext(context).build(); // <3>
77+
}
78+
79+
}
80+
----
81+
82+
<1> Specify the configuration to load
83+
<2> Inject the configuration
84+
<3> Create the `WebTestClient`
85+
86+
Internally the provided configuration is passed to `WebHttpHandlerBuilder` to set up
87+
the request processing chain, see
88+
<<web-reactive.adoc#webflux-web-handler-api,WebHandler API>> for more details. The
89+
resulting WebFlux application will be tested without an HTTP server using mock request
90+
and response objects.
91+
92+
93+
[[webtestclient-server-config]]
94+
=== Bind to server
95+
96+
This server setup option allows you to connect to a running server:
97+
98+
[source,java,intent=0]
99+
[subs="verbatim,quotes"]
100+
----
101+
client = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build();
102+
----
103+
104+
105+
[[webtestclient-client-config]]
106+
=== Client builder
107+
108+
In addition to the server setup options above, you can also configure client
109+
options including base URL, default headers, client filters, and others. These options
110+
are readily available following `bindToServer`. For all others, you need to use
111+
`configureClient()` to transition from server to client configuration as shown below:
112+
113+
[source,java,intent=0]
114+
[subs="verbatim,quotes"]
115+
----
116+
client = WebTestClient.bindToController(new TestController())
117+
.configureClient()
118+
.baseUrl("/test")
119+
.build();
120+
----
121+
122+
123+
124+
125+
[[webtestclient-tests]]
126+
== Writing tests
127+
128+
`WebTestClient` is a thin shell around <<web-reactive.adoc#webflux-webclient,WebClient>>.
129+
It provides an identical API up to the point of performing a request via `exchange()`.
130+
What follows after `exchange()` is a chained API workflow to verify responses.
131+
132+
Typically you start by asserting the response status and headers:
133+
134+
[source,java,intent=0]
135+
[subs="verbatim,quotes"]
136+
----
137+
client.get().uri("/persons/1")
138+
.accept(MediaType.APPLICATION_JSON_UTF8)
139+
.exchange()
140+
.expectStatus().isOk()
141+
.expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
142+
// ...
143+
----
144+
145+
Then you specify how to decode and consume the response body:
146+
147+
* `expectBody(Class<T>)` -- decode to single object.
148+
* `expectBodyList(Class<T>)` -- decode and collect objects to `List<T>`.
149+
* `expectBody()` -- decode to `byte[]` for <<webtestclient-json>> or empty body.
150+
151+
Then you can use built-in assertions for the body. Here is one example:
152+
153+
[source,java,intent=0]
154+
[subs="verbatim,quotes"]
155+
----
156+
client.get().uri("/persons")
157+
.exchange()
158+
.expectStatus().isOk()
159+
.expectBodyList(Person.class).hasSize(3).contains(person);
160+
----
161+
162+
You can go beyond the built-in assertions and create your own:
163+
164+
----
165+
client.get().uri("/persons/1")
166+
.exchange()
167+
.expectStatus().isOk()
168+
.expectBody(Person.class)
169+
.consumeWith(result -> {
170+
// custom assertions (e.g. AssertJ)...
171+
});
172+
----
173+
174+
You can also exit the workflow and get an `ExchangeResult` with the response data:
175+
176+
----
177+
EntityExchangeResult<Person> result = client.get().uri("/persons/1")
178+
.exchange()
179+
.expectStatus().isOk()
180+
.expectBody(Person.class)
181+
.returnResult();
182+
----
183+
184+
[TIP]
185+
====
186+
When you need to decode to a target type with generics, look for the overloaded methods
187+
that accept
188+
{api-spring-framework}/core/ParameterizedTypeReference.html[ParameterizedTypeReference]
189+
instead of `Class<T>`.
190+
====
191+
192+
193+
[[webtestclient-json]]
194+
=== JSON content
195+
196+
When you use `expectBody()` the response is consumed as a `byte[]`. This is useful for
197+
raw content assertions. For example you can use
198+
http://jsonassert.skyscreamer.org[JSONAssert] to verify JSON content:
199+
200+
[source,java,intent=0]
201+
[subs="verbatim,quotes"]
202+
----
203+
client.get().uri("/persons/1")
204+
.exchange()
205+
.expectStatus().isOk()
206+
.expectBody()
207+
.json("{\"name\":\"Jane\"}")
208+
----
209+
210+
You can also use https://github.com/jayway/JsonPath[JSONPath] expressions:
211+
212+
[source,java,intent=0]
213+
[subs="verbatim,quotes"]
214+
----
215+
client.get().uri("/persons")
216+
.exchange()
217+
.expectStatus().isOk()
218+
.expectBody()
219+
.jsonPath("$[0].name").isEqualTo("Jane")
220+
.jsonPath("$[1].name").isEqualTo("Jason");
221+
----
222+
223+
224+
[[webtestclient-stream]]
225+
=== Streaming responses
226+
227+
To test infinite streams (e.g. `"text/event-stream"`, `"application/stream+json"`),
228+
exit the response workflow via `returnResult` immediately after response status and
229+
header assertions, as shown below:
230+
231+
[source,java,intent=0]
232+
[subs="verbatim,quotes"]
233+
----
234+
FluxExchangeResult<Event> result = client.get().uri("/events")
235+
.accept(TEXT_EVENT_STREAM)
236+
.exchange()
237+
.expectStatus().isOk()
238+
.returnResult(Event.class);
239+
240+
----
241+
242+
Now you can use the `StepVerifier`, from the `reactor-test` module, to apply
243+
assertions on the stream of decoded objects and cancel when test objectives are met:
244+
245+
[source,java,intent=0]
246+
[subs="verbatim,quotes"]
247+
----
248+
Flux<Event> eventFux = result.getResponseBody();
249+
250+
StepVerifier.create(eventFlux)
251+
.expectNext(person)
252+
.expectNextCount(4)
253+
.consumeNextWith(p -> ...)
254+
.thenCancel()
255+
.verify();
256+
----
257+
258+
259+

src/docs/asciidoc/testing.adoc

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,26 @@ integration testing framework for Spring MVC. See
9292

9393
[[mock-objects-web-reactive]]
9494
==== Spring Web Reactive
95-
The `org.springframework.mock.http.server.reactive` package contains mock request and
96-
response objects for testing _Spring WebFlux_ applications. There is also a
97-
`MockWebServerExchange` in the `org.springframework.mock.web.server` package that uses
98-
those mock request and response objects.
95+
The package `org.springframework.mock.http.server.reactive` contains mock
96+
implementations of `ServerHttpRequest` and `ServerHttpResponse` for use in WebFlux
97+
applications. The package `org.springframework.mock.web.server`
98+
contains a mock `ServerWebExchange` that depends on those mock request and response
99+
objects.
99100

100-
`WebTestClient` builds on these mock objects to provide integration testing support for
101-
WebFlux server endpoints without running an HTTP server.
101+
Both `MockServerHttpRequest` and `MockServerHttpResponse` extend from the same
102+
abstract base classes as server-specific implementations do and share behavior with them.
103+
For example a mock request is immutable once created but you can use the `mutate()` method
104+
from `ServerHttpRequest` to create a modified instance.
105+
106+
In order for the mock response to properly implement the write contract and return a
107+
write completion handle (i.e. `Mono<Void>`), by default it uses a `Flux` with
108+
`cache().then()`, which buffers the data and makes it available for assertions in
109+
tests. Applications can set a custom write function for example to test an infinite
110+
stream.
111+
112+
The <<webtestclient>> builds on the mock request and response to provide support for
113+
testing WebFlux applications without an HTTP server. The client can also be used for
114+
end-to-end tests with a running server.
102115

103116

104117
[[unit-testing-support-classes]]
@@ -5553,6 +5566,8 @@ https://github.com/spring-projects/spring-framework/tree/master/spring-test/src/
55535566
tests] of client-side REST tests.
55545567

55555568

5569+
include::testing-webtestclient.adoc[leveloffset=+2]
5570+
55565571

55575572
[[testing-examples-petclinic]]
55585573
=== PetClinic Example

src/docs/asciidoc/web/webflux.adoc

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ and co-exist side by side in the Spring Framework. Each module is optional.
1616
Applications may use one or the other module, or in some cases both --
1717
e.g. Spring MVC controllers with the reactive `WebClient`.
1818

19-
In addition to the web framework, Spring WebFlux also provides a <<webflux-client>> for
20-
performing HTTP requests, a `WebTestClient` for testing web endpoints, and
21-
also client and server reactive, WebSocket support.
19+
The `spring-webflux` module also provides a reactive <<webflux-client,WebClient>> for
20+
performing HTTP requests, along with client and server, reactive WebSocket support.
21+
The `spring-test` module provides test support for WebFlux applications, see
22+
<<webflux-test>> for more details.
2223

2324

2425
[[webflux-new-framework]]
@@ -1389,6 +1390,19 @@ the classpath.
13891390
include::webflux-webclient.adoc[leveloffset=+1]
13901391

13911392

1393+
[[webflux-test]]
1394+
== Testing
1395+
1396+
The `spring-test` module provides mock implementations of `ServerHttpRequest`,
1397+
`ServerHttpResponse`, and `ServerWebExchange`.
1398+
See <<testing.adoc#mock-objects-web-reactive,Spring Web Reactive>> mock objects.
1399+
1400+
The <<testing.adoc#webtestclient,WebTestClient>> builds on these mock request and
1401+
response objects to provide support for testing WebFlux applications without and HTTP
1402+
server. The `WebTestClient` can be used for end-to-end integration tests too.
1403+
1404+
1405+
13921406
[[webflux-reactive-libraries]]
13931407
== Reactive Libraries
13941408

0 commit comments

Comments
 (0)