Documentation for WebTestClient
Issue: SPR-16009
This commit is contained in:
259
src/docs/asciidoc/testing-webtestclient.adoc
Normal file
259
src/docs/asciidoc/testing-webtestclient.adoc
Normal file
@@ -0,0 +1,259 @@
|
||||
[[webtestclient]]
|
||||
= WebTestClient
|
||||
|
||||
`WebTestClient` is a non-blocking, reactive client for testing web servers. It uses
|
||||
the reactive <<web-reactive.adoc#webflux-webclient, WebClient>> internally to perform
|
||||
requests and provides a fluent API to verify responses. The `WebTestClient` can connect
|
||||
to any server over an HTTP connection. It can also bind directly to WebFlux applications
|
||||
with <<testing.adoc#mock-objects-web-reactive,mock request and response>> objects,
|
||||
without the need for an HTTP server.
|
||||
|
||||
|
||||
|
||||
[[webtestclient-setup]]
|
||||
== Setup
|
||||
|
||||
To create a `WebTestClient` you must choose one of several server setup options.
|
||||
Effectively you either configure a WebFlux application to bind to, or use absolute URLs
|
||||
to connect to a running server.
|
||||
|
||||
|
||||
[[webtestclient-controller-config]]
|
||||
=== Bind to controller
|
||||
|
||||
Use this server setup to test one `@Controller` at a time:
|
||||
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
client = WebTestClient.bindToController(new TestController()).build();
|
||||
----
|
||||
|
||||
The above loads the <<web-reactive.adoc#webflux-config,WebFlux Java config>> and
|
||||
registers the given controller. The resulting WebFlux application will be tested
|
||||
without an HTTP server using mock request and response objects. There are more methods
|
||||
on the builder to customize the default WebFlux Java config.
|
||||
|
||||
|
||||
[[webtestclient-fn-config]]
|
||||
=== Bind to RouterFunction
|
||||
|
||||
Use this option to set up a server from a
|
||||
<<web-reactive.adoc#webflux-fn,RouterFunction>>:
|
||||
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
RouterFunction<?> route = ...
|
||||
client = WebTestClient.bindToRouterFunction(route).build();
|
||||
----
|
||||
|
||||
Internally the provided configuration is passed to `RouterFunctions.toWebHandler`.
|
||||
The resulting WebFlux application will be tested without an HTTP server using mock
|
||||
request and response objects
|
||||
|
||||
|
||||
[[webtestclient-context-config]]
|
||||
=== Bind to ApplicationContext
|
||||
|
||||
Use this option to setup a server from the Spring configuration of your application, or
|
||||
some subset of it:
|
||||
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = WebConfig.class) // <1>
|
||||
public class MyTests {
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext context; // <2>
|
||||
|
||||
private WebTestClient client;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
client = WebTestClient.bindToApplicationContext(context).build(); // <3>
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
<1> Specify the configuration to load
|
||||
<2> Inject the configuration
|
||||
<3> Create the `WebTestClient`
|
||||
|
||||
Internally the provided configuration is passed to `WebHttpHandlerBuilder` to set up
|
||||
the request processing chain, see
|
||||
<<web-reactive.adoc#webflux-web-handler-api,WebHandler API>> for more details. The
|
||||
resulting WebFlux application will be tested without an HTTP server using mock request
|
||||
and response objects.
|
||||
|
||||
|
||||
[[webtestclient-server-config]]
|
||||
=== Bind to server
|
||||
|
||||
This server setup option allows you to connect to a running server:
|
||||
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
client = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build();
|
||||
----
|
||||
|
||||
|
||||
[[webtestclient-client-config]]
|
||||
=== Client builder
|
||||
|
||||
In addition to the server setup options above, you can also configure client
|
||||
options including base URL, default headers, client filters, and others. These options
|
||||
are readily available following `bindToServer`. For all others, you need to use
|
||||
`configureClient()` to transition from server to client configuration as shown below:
|
||||
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
client = WebTestClient.bindToController(new TestController())
|
||||
.configureClient()
|
||||
.baseUrl("/test")
|
||||
.build();
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
[[webtestclient-tests]]
|
||||
== Writing tests
|
||||
|
||||
`WebTestClient` is a thin shell around <<web-reactive.adoc#webflux-webclient,WebClient>>.
|
||||
It provides an identical API up to the point of performing a request via `exchange()`.
|
||||
What follows after `exchange()` is a chained API workflow to verify responses.
|
||||
|
||||
Typically you start by asserting the response status and headers:
|
||||
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
client.get().uri("/persons/1")
|
||||
.accept(MediaType.APPLICATION_JSON_UTF8)
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
|
||||
// ...
|
||||
----
|
||||
|
||||
Then you specify how to decode and consume the response body:
|
||||
|
||||
* `expectBody(Class<T>)` -- decode to single object.
|
||||
* `expectBodyList(Class<T>)` -- decode and collect objects to `List<T>`.
|
||||
* `expectBody()` -- decode to `byte[]` for <<webtestclient-json>> or empty body.
|
||||
|
||||
Then you can use built-in assertions for the body. Here is one example:
|
||||
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
client.get().uri("/persons")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBodyList(Person.class).hasSize(3).contains(person);
|
||||
----
|
||||
|
||||
You can go beyond the built-in assertions and create your own:
|
||||
|
||||
----
|
||||
client.get().uri("/persons/1")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody(Person.class)
|
||||
.consumeWith(result -> {
|
||||
// custom assertions (e.g. AssertJ)...
|
||||
});
|
||||
----
|
||||
|
||||
You can also exit the workflow and get an `ExchangeResult` with the response data:
|
||||
|
||||
----
|
||||
EntityExchangeResult<Person> result = client.get().uri("/persons/1")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody(Person.class)
|
||||
.returnResult();
|
||||
----
|
||||
|
||||
[TIP]
|
||||
====
|
||||
When you need to decode to a target type with generics, look for the overloaded methods
|
||||
that accept
|
||||
{api-spring-framework}/core/ParameterizedTypeReference.html[ParameterizedTypeReference]
|
||||
instead of `Class<T>`.
|
||||
====
|
||||
|
||||
|
||||
[[webtestclient-json]]
|
||||
=== JSON content
|
||||
|
||||
When you use `expectBody()` the response is consumed as a `byte[]`. This is useful for
|
||||
raw content assertions. For example you can use
|
||||
http://jsonassert.skyscreamer.org[JSONAssert] to verify JSON content:
|
||||
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
client.get().uri("/persons/1")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody()
|
||||
.json("{\"name\":\"Jane\"}")
|
||||
----
|
||||
|
||||
You can also use https://github.com/jayway/JsonPath[JSONPath] expressions:
|
||||
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
client.get().uri("/persons")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody()
|
||||
.jsonPath("$[0].name").isEqualTo("Jane")
|
||||
.jsonPath("$[1].name").isEqualTo("Jason");
|
||||
----
|
||||
|
||||
|
||||
[[webtestclient-stream]]
|
||||
=== Streaming responses
|
||||
|
||||
To test infinite streams (e.g. `"text/event-stream"`, `"application/stream+json"`),
|
||||
exit the response workflow via `returnResult` immediately after response status and
|
||||
header assertions, as shown below:
|
||||
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
FluxExchangeResult<Event> result = client.get().uri("/events")
|
||||
.accept(TEXT_EVENT_STREAM)
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.returnResult(Event.class);
|
||||
|
||||
----
|
||||
|
||||
Now you can use the `StepVerifier`, from the `reactor-test` module, to apply
|
||||
assertions on the stream of decoded objects and cancel when test objectives are met:
|
||||
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
Flux<Event> eventFux = result.getResponseBody();
|
||||
|
||||
StepVerifier.create(eventFlux)
|
||||
.expectNext(person)
|
||||
.expectNextCount(4)
|
||||
.consumeNextWith(p -> ...)
|
||||
.thenCancel()
|
||||
.verify();
|
||||
----
|
||||
|
||||
|
||||
|
||||
@@ -92,13 +92,26 @@ integration testing framework for Spring MVC. See
|
||||
|
||||
[[mock-objects-web-reactive]]
|
||||
==== Spring Web Reactive
|
||||
The `org.springframework.mock.http.server.reactive` package contains mock request and
|
||||
response objects for testing _Spring WebFlux_ applications. There is also a
|
||||
`MockWebServerExchange` in the `org.springframework.mock.web.server` package that uses
|
||||
those mock request and response objects.
|
||||
The package `org.springframework.mock.http.server.reactive` contains mock
|
||||
implementations of `ServerHttpRequest` and `ServerHttpResponse` for use in WebFlux
|
||||
applications. The package `org.springframework.mock.web.server`
|
||||
contains a mock `ServerWebExchange` that depends on those mock request and response
|
||||
objects.
|
||||
|
||||
`WebTestClient` builds on these mock objects to provide integration testing support for
|
||||
WebFlux server endpoints without running an HTTP server.
|
||||
Both `MockServerHttpRequest` and `MockServerHttpResponse` extend from the same
|
||||
abstract base classes as server-specific implementations do and share behavior with them.
|
||||
For example a mock request is immutable once created but you can use the `mutate()` method
|
||||
from `ServerHttpRequest` to create a modified instance.
|
||||
|
||||
In order for the mock response to properly implement the write contract and return a
|
||||
write completion handle (i.e. `Mono<Void>`), by default it uses a `Flux` with
|
||||
`cache().then()`, which buffers the data and makes it available for assertions in
|
||||
tests. Applications can set a custom write function for example to test an infinite
|
||||
stream.
|
||||
|
||||
The <<webtestclient>> builds on the mock request and response to provide support for
|
||||
testing WebFlux applications without an HTTP server. The client can also be used for
|
||||
end-to-end tests with a running server.
|
||||
|
||||
|
||||
[[unit-testing-support-classes]]
|
||||
@@ -5553,6 +5566,8 @@ https://github.com/spring-projects/spring-framework/tree/master/spring-test/src/
|
||||
tests] of client-side REST tests.
|
||||
|
||||
|
||||
include::testing-webtestclient.adoc[leveloffset=+2]
|
||||
|
||||
|
||||
[[testing-examples-petclinic]]
|
||||
=== PetClinic Example
|
||||
|
||||
@@ -16,9 +16,10 @@ and co-exist side by side in the Spring Framework. Each module is optional.
|
||||
Applications may use one or the other module, or in some cases both --
|
||||
e.g. Spring MVC controllers with the reactive `WebClient`.
|
||||
|
||||
In addition to the web framework, Spring WebFlux also provides a <<webflux-client>> for
|
||||
performing HTTP requests, a `WebTestClient` for testing web endpoints, and
|
||||
also client and server reactive, WebSocket support.
|
||||
The `spring-webflux` module also provides a reactive <<webflux-client,WebClient>> for
|
||||
performing HTTP requests, along with client and server, reactive WebSocket support.
|
||||
The `spring-test` module provides test support for WebFlux applications, see
|
||||
<<webflux-test>> for more details.
|
||||
|
||||
|
||||
[[webflux-new-framework]]
|
||||
@@ -1389,6 +1390,19 @@ the classpath.
|
||||
include::webflux-webclient.adoc[leveloffset=+1]
|
||||
|
||||
|
||||
[[webflux-test]]
|
||||
== Testing
|
||||
|
||||
The `spring-test` module provides mock implementations of `ServerHttpRequest`,
|
||||
`ServerHttpResponse`, and `ServerWebExchange`.
|
||||
See <<testing.adoc#mock-objects-web-reactive,Spring Web Reactive>> mock objects.
|
||||
|
||||
The <<testing.adoc#webtestclient,WebTestClient>> builds on these mock request and
|
||||
response objects to provide support for testing WebFlux applications without and HTTP
|
||||
server. The `WebTestClient` can be used for end-to-end integration tests too.
|
||||
|
||||
|
||||
|
||||
[[webflux-reactive-libraries]]
|
||||
== Reactive Libraries
|
||||
|
||||
|
||||
Reference in New Issue
Block a user