Documentation for WebTestClient

Issue: SPR-16009
This commit is contained in:
Rossen Stoyanchev
2017-10-03 09:56:13 -04:00
parent 2ecd5a4f02
commit fbf2e1e355
3 changed files with 297 additions and 9 deletions

View 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();
----

View File

@@ -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

View File

@@ -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