diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client/standalone/resultmatches/ModelAssertionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client/standalone/resultmatches/ModelAssertionTests.java index 76384b3b43..7bb811a96c 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client/standalone/resultmatches/ModelAssertionTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client/standalone/resultmatches/ModelAssertionTests.java @@ -58,6 +58,22 @@ public class ModelAssertionTests { .alwaysExpect(status().isOk()) .build(); + @Test + void name() throws Exception { + + EntityExchangeResult result = client.get().uri("/path") + .exchange() + .expectBody().isEmpty(); + + MockMvcWebTestClient.resultActionsFor(result) + .andExpect(model().attribute("integer", 3)) + .andExpect(model().attribute("string", "a string value")) + .andExpect(model().attribute("integer", equalTo(3))) // Hamcrest... + .andExpect(model().attribute("string", equalTo("a string value"))) + .andExpect(model().attribute("globalAttrName", equalTo("Global Attribute Value"))); + + } + @Test void attributeEqualTo() throws Exception { performRequest(HttpMethod.GET, "/") diff --git a/src/docs/asciidoc/testing-webtestclient.adoc b/src/docs/asciidoc/testing-webtestclient.adoc index 940c945606..908d03d608 100644 --- a/src/docs/asciidoc/testing-webtestclient.adoc +++ b/src/docs/asciidoc/testing-webtestclient.adoc @@ -29,9 +29,10 @@ of several mock server setup choices or a connection to a live server. This setup allows you to test specific controller(s) via mock request and response objects, without a running server. -For WebFlux applications, use the below which loads the -<> and registers the given -controller(s) to handle requests with: +For WebFlux applications, use the below which loads infrastructure equivalent to the +<>, registers the given +controller(s), and creates a <> +to handle requests with: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java @@ -45,9 +46,11 @@ controller(s) to handle requests with: val client = WebTestClient.bindToController(TestController()).build() ---- -For Spring MVC, use the below which loads infrastructure equivalent to the -<> and registers the given controller(s) to -handle requests with via <>: +For Spring MVC, use the below which delegates to the +{api-spring-framework}/https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/web/servlet/setup/StandaloneMockMvcBuilder.html[StandaloneMockMvcBuilder] +to load infrastructure equivalent to the <>, +registers the given controller(s), and creates an instance of +<> to handle requests with: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java @@ -66,13 +69,14 @@ handle requests with via <>: [[webtestclient-context-config]] === Bind to `ApplicationContext` -This setup allows you to point to Spring configuration with Spring MVC or Spring WebFlux -infrastructure and controller declarations which is then exercised via mock request and -response objects, without a running server. +This setup allows you to load Spring configuration with Spring MVC or Spring WebFlux +infrastructure and controller declarations and use it to handle requests via mock request +and response objects, without a running server. -For WebFlux, use the below in which the given Spring `ApplicationContext` is passed to -`WebHttpHandlerBuilder` to create the -<> to handle requests with: +For WebFlux, use the below where the Spring `ApplicationContext` is passed to +{api-spring-framework}/web/server/adapter/WebHttpHandlerBuilder.html#applicationContext-org.springframework.context.ApplicationContext-[WebHttpHandlerBuilder] +to create the <> to handle +requests with: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java @@ -110,9 +114,10 @@ For WebFlux, use the below in which the given Spring `ApplicationContext` is pas <2> Inject the configuration <3> Create the `WebTestClient` -For Spring MVC, use the below in which the given Spring `ApplicationContext` is passed -to `MockMvcBuilders#webAppContextSetup` to create a -<> to handle requests with: +For Spring MVC, use the below where the Spring `ApplicationContext` is passed to +{api-spring-framework}/test/web/servlet/setup/MockMvcBuilders.html#webAppContextSetup-org.springframework.web.context.WebApplicationContext-[MockMvcBuilders.html#webAppContextSetup] +to create a <> instance to handle +requests with: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java @@ -198,7 +203,7 @@ For Spring MVC there is currently no options to test [[webtestclient-server-config]] === Bind to Server -The following server setup option lets you connect to a running server: +This setup connects to a running server to perform full, end-to-end HTTP tests: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java @@ -245,10 +250,14 @@ are readily available following `bindToServer`. For all others, you need to use == Writing Tests `WebTestClient` provides an API identical to <> -up to the point of performing a request by using `exchange()`. What follows after -`exchange()` is a chained API workflow to verify responses. +up to the point of performing a request by using `exchange()`. See the +<> documentation for examples on how to +prepare a request with any content including form data, multipart data, and more. -Typically, you start by asserting the response status and headers, as follows: +After the call to `exchange()`, `WebTestClient` diverges from the `WebClient` and +instead continues with a workflow to verify responses. + +To assert the response status and headers, use the below: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java @@ -269,13 +278,13 @@ Typically, you start by asserting the response status and headers, as follows: .expectHeader().contentType(MediaType.APPLICATION_JSON) ---- -Then you specify how to decode and consume the response body: +You can then choose to decode the response body through one of the following: * `expectBody(Class)`: Decode to single object. * `expectBodyList(Class)`: Decode and collect objects to `List`. * `expectBody()`: Decode to `byte[]` for <> or an empty body. -Then you can use built-in assertions for the body. The following example shows one way to do so: +And perform assertions on the resulting higher level Object(s): [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java @@ -296,7 +305,8 @@ Then you can use built-in assertions for the body. The following example shows o .expectBodyList().hasSize(3).contains(person) ---- -You can also go beyond the built-in assertions and create your own, as the following example shows: +If the built-in assertions are insufficient, you can consume the object instead and +perform any other assertions: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java @@ -323,7 +333,7 @@ You can also go beyond the built-in assertions and create your own, as the follo } ---- -You can also exit the workflow and get a result, as follows: +Or you can exit the workflow and obtain an `EntityExchangeResult`: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java @@ -356,27 +366,7 @@ instead of `Class`. [[webtestclient-no-content]] === No Content -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,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: +If the response is not expected to have content, you can assert that as follows: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java @@ -397,13 +387,35 @@ Alternatively, if you want to assert there is no response content, you can use c .expectBody().isEmpty() ---- +If you want to ignore the response content, the following releases the content without +any assertions: + +[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() +---- + + [[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 -https://jsonassert.skyscreamer.org[JSONAssert] to verify JSON content, as follows: +You can use `expectBody()` without a target type to perform assertions on the raw +content rather than through higher level Object(s). + +To verify the full JSON content with https://jsonassert.skyscreamer.org[JSONAssert]: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java @@ -424,7 +436,7 @@ https://jsonassert.skyscreamer.org[JSONAssert] to verify JSON content, as follow .json("{\"name\":\"Jane\"}") ---- -You can also use https://github.com/jayway/JsonPath[JSONPath] expressions, as follows: +To verify JSON content with https://github.com/jayway/JsonPath[JSONPath]: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java @@ -452,9 +464,9 @@ You can also use https://github.com/jayway/JsonPath[JSONPath] expressions, as fo [[webtestclient-stream]] === Streaming Responses -To test infinite streams (for example, `"text/event-stream"` or `"application/x-ndjson"`), -you need to exit the chained API (by using `returnResult`), immediately after the response status -and header assertions, as the following example shows: +To test potentially infinite streams such as `"text/event-stream"` or +`"application/x-ndjson"`, start by verifying the response status and headers, and then +obtain a `FluxExchangeResult`: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java @@ -478,9 +490,7 @@ and header assertions, as the following example shows: .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: +Now you're ready to consume the response stream with `StepVerifier` from `reactor-test`: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java @@ -508,12 +518,61 @@ from the `reactor-test` module to do that, as the following example shows: ---- +[[webtestclient-mockmvc]] +=== MockMvc Assertions -[[webtestclient-request-body]] -=== Request Body +`WebTestClient` is an HTTP client and as such it can only verify what is in the client +response including status, headers, and body. + +When testing a Spring MVC application with a MockMvc server setup, you have the extra +choice to perform further assertions on the server response. To do that start by +obtaining an `ExchangeResult` after asserting the body: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + // For a response with a body + EntityExchangeResult result = client.get().uri("/persons/1") + .exchange() + .expectStatus().isOk() + .expectBody(Person.class) + .returnResult(); + + // For a response without a body + EntityExchangeResult result = client.get().uri("/path") + .exchange() + .expectBody().isEmpty(); +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // For a response with a body + val result = client.get().uri("/persons/1") + .exchange() + .expectStatus().isOk() + .expectBody(Person.class) + .returnResult(); + + // For a response without a body + val result = client.get().uri("/path") + .exchange() + .expectBody().isEmpty(); +---- + +Then switch to MockMvc server response assertions: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + MockMvcWebTestClient.resultActionsFor(result) + .andExpect(model().attribute("integer", 3)) + .andExpect(model().attribute("string", "a string value")); +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + MockMvcWebTestClient.resultActionsFor(result) + .andExpect(model().attribute("integer", 3)) + .andExpect(model().attribute("string", "a string value")); +---- -When it comes to building requests, the `WebTestClient` offers an API identical to -the `WebClient`, and the implementation is mostly a simple pass-through. See the -<> for examples on -how to prepare a request with a body, including submitting form data, multipart requests, -and more.