diff --git a/src/docs/asciidoc/testing-webtestclient.adoc b/src/docs/asciidoc/testing-webtestclient.adoc index daa1a74b13b..650135701de 100644 --- a/src/docs/asciidoc/testing-webtestclient.adoc +++ b/src/docs/asciidoc/testing-webtestclient.adoc @@ -27,11 +27,16 @@ a URL to connect to a running server. The following example shows how to create a server setup to test one `@Controller` at a time: -[source,java,intent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- client = WebTestClient.bindToController(new TestController()).build(); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + client = WebTestClient.bindToController(TestController()).build() +---- The preceding example loads the <> and registers the given controller. The resulting WebFlux application is tested @@ -46,12 +51,18 @@ on the builder to customize the default WebFlux Java configuration. The following example shows how to set up a server from a <>: -[source,java,intent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- RouterFunction route = ... client = WebTestClient.bindToRouterFunction(route).build(); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val route: RouterFunction<*> = ... + val client = WebTestClient.bindToRouterFunction(route).build() +---- Internally, the configuration is passed to `RouterFunctions.toWebHandler`. The resulting WebFlux application is tested without an HTTP server by using mock @@ -65,11 +76,10 @@ request and response objects. The following example shows how to setup a server from the Spring configuration of your application or some subset of it: -[source,java,intent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - @RunWith(SpringRunner.class) - @ContextConfiguration(classes = WebConfig.class) // <1> + @SpringJUnitConfig(WebConfig.class) // <1> public class MyTests { @Autowired @@ -77,13 +87,33 @@ some subset of it: private WebTestClient client; - @Before + @BeforeEach public void setUp() { client = WebTestClient.bindToApplicationContext(context).build(); // <3> } } ---- +<1> Specify the configuration to load +<2> Inject the configuration +<3> Create the `WebTestClient` + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @SpringJUnitConfig(WebConfig::class) // <1> + class MyTests { + @Autowired + lateinit var context: ApplicationContext // <2> + + lateinit var client: WebTestClient + + @BeforeEach + fun setUp() { + client = WebTestClient.bindToApplicationContext(context).build() // <3> + } + } +---- <1> Specify the configuration to load <2> Inject the configuration <3> Create the `WebTestClient` @@ -100,11 +130,16 @@ using mock request and response objects. The following server setup option lets you connect to a running server: -[source,java,intent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- client = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build(); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + client = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build() +---- @@ -116,14 +151,22 @@ options, including base URL, default headers, client filters, and others. These are readily available following `bindToServer`. For all others, you need to use `configureClient()` to transition from server to client configuration, as follows: -[source,java,intent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- client = WebTestClient.bindToController(new TestController()) .configureClient() .baseUrl("/test") .build(); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + client = WebTestClient.bindToController(TestController()) + .configureClient() + .baseUrl("/test") + .build() +---- @@ -137,15 +180,23 @@ up to the point of performing a request by using `exchange()`. What follows afte Typically, you start by asserting the response status and headers, as follows: -[source,java,intent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + client.get().uri("/persons/1") + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus().isOk() + .expectHeader().contentType(MediaType.APPLICATION_JSON) +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin ---- client.get().uri("/persons/1") .accept(MediaType.APPLICATION_JSON) .exchange() .expectStatus().isOk() .expectHeader().contentType(MediaType.APPLICATION_JSON) - // ... ---- Then you specify how to decode and consume the response body: @@ -156,18 +207,32 @@ Then you specify how to decode and consume the response body: Then you can use built-in assertions for the body. The following example shows one way to do so: -[source,java,intent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- client.get().uri("/persons") .exchange() .expectStatus().isOk() .expectBodyList(Person.class).hasSize(3).contains(person); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + import org.springframework.test.web.reactive.server.expectBodyList + + client.get().uri("/persons") + .exchange() + .expectStatus().isOk() + .expectBodyList().hasSize(3).contains(person) +---- You can also go beyond the built-in assertions and create your own, as the following example shows: +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- + import org.springframework.test.web.reactive.server.expectBody + client.get().uri("/persons/1") .exchange() .expectStatus().isOk() @@ -176,9 +241,22 @@ You can also go beyond the built-in assertions and create your own, as the follo // custom assertions (e.g. AssertJ)... }); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + client.get().uri("/persons/1") + .exchange() + .expectStatus().isOk() + .expectBody() + .consumeWith { + // custom assertions (e.g. AssertJ)... + } +---- You can also exit the workflow and get a result, as follows: +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- EntityExchangeResult result = client.get().uri("/persons/1") .exchange() @@ -186,6 +264,17 @@ You can also exit the workflow and get a result, as follows: .expectBody(Person.class) .returnResult(); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + import org.springframework.test.web.reactive.server.expectBody + + val result = client.get().uri("/persons/1") + .exchange() + .expectStatus().isOk + .expectBody() + .returnResult() +---- TIP: When you need to decode to a target type with generics, look for the overloaded methods that accept @@ -200,19 +289,27 @@ instead of `Class`. If the response has no content (or you do not care if it does) use `Void.class`, which ensures that resources are released. The following example shows how to do so: -[source,java,intent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- client.get().uri("/persons/123") .exchange() .expectStatus().isNotFound() .expectBody(Void.class); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + client.get().uri("/persons/123") + .exchange() + .expectStatus().isNotFound + .expectBody() +---- Alternatively, if you want to assert there is no response content, you can use code similar to the following: -[source,java,intent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- client.post().uri("/persons") .body(personMono, Person.class) @@ -220,7 +317,15 @@ Alternatively, if you want to assert there is no response content, you can use c .expectStatus().isCreated() .expectBody().isEmpty(); ---- - +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + client.post().uri("/persons") + .bodyValue(person) + .exchange() + .expectStatus().isCreated() + .expectBody().isEmpty() +---- [[webtestclient-json]] @@ -230,8 +335,17 @@ When you use `expectBody()`, the response is consumed as a `byte[]`. This is use raw content assertions. For example, you can use https://jsonassert.skyscreamer.org[JSONAssert] to verify JSON content, as follows: -[source,java,intent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + client.get().uri("/persons/1") + .exchange() + .expectStatus().isOk() + .expectBody() + .json("{\"name\":\"Jane\"}") +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin ---- client.get().uri("/persons/1") .exchange() @@ -242,8 +356,8 @@ https://jsonassert.skyscreamer.org[JSONAssert] to verify JSON content, as follow You can also use https://github.com/jayway/JsonPath[JSONPath] expressions, as follows: -[source,java,intent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- client.get().uri("/persons") .exchange() @@ -252,6 +366,16 @@ You can also use https://github.com/jayway/JsonPath[JSONPath] expressions, as fo .jsonPath("$[0].name").isEqualTo("Jane") .jsonPath("$[1].name").isEqualTo("Jason"); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + client.get().uri("/persons") + .exchange() + .expectStatus().isOk() + .expectBody() + .jsonPath("$[0].name").isEqualTo("Jane") + .jsonPath("$[1].name").isEqualTo("Jason") +---- @@ -262,8 +386,8 @@ To test infinite streams (for example, `"text/event-stream"` or `"application/st you need to exit the chained API (by using `returnResult`), immediately after the response status and header assertions, as the following example shows: -[source,java,intent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- FluxExchangeResult result = client.get().uri("/events") .accept(TEXT_EVENT_STREAM) @@ -272,15 +396,26 @@ and header assertions, as the following example shows: .returnResult(MyEvent.class); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + import org.springframework.test.web.reactive.server.returnResult + + val result = client.get().uri("/events") + .accept(TEXT_EVENT_STREAM) + .exchange() + .expectStatus().isOk() + .returnResult() +---- Now you can consume the `Flux`, assert decoded objects as they come, and then cancel at some point when test objectives are met. We recommend using the `StepVerifier` from the `reactor-test` module to do that, as the following example shows: -[source,java,intent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - Flux eventFux = result.getResponseBody(); + Flux eventFlux = result.getResponseBody(); StepVerifier.create(eventFlux) .expectNext(person) @@ -289,6 +424,18 @@ from the `reactor-test` module to do that, as the following example shows: .thenCancel() .verify(); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val eventFlux = result.getResponseBody() + + StepVerifier.create(eventFlux) + .expectNext(person) + .expectNextCount(4) + .consumeNextWith { p -> ... } + .thenCancel() + .verify() +----