720 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
			
		
		
	
	
			720 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
| [[webtestclient]]
 | |
| = WebTestClient
 | |
| 
 | |
| `WebTestClient` is an HTTP client designed for testing server applications. It wraps
 | |
| Spring's xref:web/webflux-webclient.adoc[WebClient] and uses it to perform requests
 | |
| but exposes a testing facade for verifying responses. `WebTestClient` can be used to
 | |
| perform end-to-end HTTP tests. It can also be used to test Spring MVC and Spring WebFlux
 | |
| applications without a running server via mock server request and response objects.
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| [[webtestclient-setup]]
 | |
| == Setup
 | |
| 
 | |
| To set up a `WebTestClient` you need to choose a server setup to bind to. This can be one
 | |
| of several mock server setup choices or a connection to a live server.
 | |
| 
 | |
| 
 | |
| 
 | |
| [[webtestclient-controller-config]]
 | |
| === Bind to Controller
 | |
| 
 | |
| This setup allows you to test specific controller(s) via mock request and response objects,
 | |
| without a running server.
 | |
| 
 | |
| For WebFlux applications, use the following which loads infrastructure equivalent to the
 | |
| xref:web/webflux/dispatcher-handler.adoc#webflux-framework-config[WebFlux Java config], registers the given
 | |
| controller(s), and creates a xref:web/webflux/reactive-spring.adoc#webflux-web-handler-api[WebHandler chain]
 | |
| to handle requests:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,indent=0,subs="verbatim,quotes",role="primary"]
 | |
| ----
 | |
| 	WebTestClient client =
 | |
| 			WebTestClient.bindToController(new TestController()).build();
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
 | |
| ----
 | |
| 	val client = WebTestClient.bindToController(TestController()).build()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| For Spring MVC, use the following which delegates to the
 | |
| {spring-framework-api}/test/web/servlet/setup/StandaloneMockMvcBuilder.html[StandaloneMockMvcBuilder]
 | |
| to load infrastructure equivalent to the xref:web/webmvc/mvc-config.adoc[WebMvc Java config],
 | |
| registers the given controller(s), and creates an instance of
 | |
| xref:testing/mockmvc.adoc[MockMvc] to handle requests:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,indent=0,subs="verbatim,quotes",role="primary"]
 | |
| ----
 | |
| 	WebTestClient client =
 | |
| 			MockMvcWebTestClient.bindToController(new TestController()).build();
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
 | |
| ----
 | |
| 	val client = MockMvcWebTestClient.bindToController(TestController()).build()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| 
 | |
| 
 | |
| [[webtestclient-context-config]]
 | |
| === Bind to `ApplicationContext`
 | |
| 
 | |
| 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 following where the Spring `ApplicationContext` is passed to
 | |
| {spring-framework-api}/web/server/adapter/WebHttpHandlerBuilder.html#applicationContext-org.springframework.context.ApplicationContext-[WebHttpHandlerBuilder]
 | |
| to create the xref:web/webflux/reactive-spring.adoc#webflux-web-handler-api[WebHandler chain] to handle
 | |
| requests:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,indent=0,subs="verbatim,quotes",role="primary"]
 | |
| ----
 | |
| 	@SpringJUnitConfig(WebConfig.class) // <1>
 | |
| 	class MyTests {
 | |
| 
 | |
| 		WebTestClient client;
 | |
| 
 | |
| 		@BeforeEach
 | |
| 		void setUp(ApplicationContext context) {  // <2>
 | |
| 			client = WebTestClient.bindToApplicationContext(context).build(); // <3>
 | |
| 		}
 | |
| 	}
 | |
| ----
 | |
| <1> Specify the configuration to load
 | |
| <2> Inject the configuration
 | |
| <3> Create the `WebTestClient`
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
 | |
| ----
 | |
| 	@SpringJUnitConfig(WebConfig::class) // <1>
 | |
| 	class MyTests {
 | |
| 
 | |
| 		lateinit var client: WebTestClient
 | |
| 
 | |
| 		@BeforeEach
 | |
| 		fun setUp(context: ApplicationContext) { // <2>
 | |
| 			client = WebTestClient.bindToApplicationContext(context).build() // <3>
 | |
| 		}
 | |
| 	}
 | |
| ----
 | |
| <1> Specify the configuration to load
 | |
| <2> Inject the configuration
 | |
| <3> Create the `WebTestClient`
 | |
| ======
 | |
| 
 | |
| For Spring MVC, use the following where the Spring `ApplicationContext` is passed to
 | |
| {spring-framework-api}/test/web/servlet/setup/MockMvcBuilders.html#webAppContextSetup-org.springframework.web.context.WebApplicationContext-[MockMvcBuilders.webAppContextSetup]
 | |
| to create a xref:testing/mockmvc.adoc[MockMvc] instance to handle
 | |
| requests:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,indent=0,subs="verbatim,quotes",role="primary"]
 | |
| ----
 | |
| 	@ExtendWith(SpringExtension.class)
 | |
| 	@WebAppConfiguration("classpath:META-INF/web-resources") // <1>
 | |
| 	@ContextHierarchy({
 | |
| 		@ContextConfiguration(classes = RootConfig.class),
 | |
| 		@ContextConfiguration(classes = WebConfig.class)
 | |
| 	})
 | |
| 	class MyTests {
 | |
| 
 | |
| 		@Autowired
 | |
| 		WebApplicationContext wac; // <2>
 | |
| 
 | |
| 		WebTestClient client;
 | |
| 
 | |
| 		@BeforeEach
 | |
| 		void setUp() {
 | |
| 			client = MockMvcWebTestClient.bindToApplicationContext(this.wac).build(); // <3>
 | |
| 		}
 | |
| 	}
 | |
| ----
 | |
| <1> Specify the configuration to load
 | |
| <2> Inject the configuration
 | |
| <3> Create the `WebTestClient`
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
 | |
| ----
 | |
| 	@ExtendWith(SpringExtension.class)
 | |
| 	@WebAppConfiguration("classpath:META-INF/web-resources") // <1>
 | |
| 	@ContextHierarchy({
 | |
| 		@ContextConfiguration(classes = RootConfig.class),
 | |
| 		@ContextConfiguration(classes = WebConfig.class)
 | |
| 	})
 | |
| 	class MyTests {
 | |
| 
 | |
| 		@Autowired
 | |
| 		lateinit var wac: WebApplicationContext; // <2>
 | |
| 
 | |
| 		lateinit var client: WebTestClient
 | |
| 
 | |
| 		@BeforeEach
 | |
| 		fun setUp() { // <2>
 | |
| 			client = MockMvcWebTestClient.bindToApplicationContext(wac).build() // <3>
 | |
| 		}
 | |
| 	}
 | |
| ----
 | |
| <1> Specify the configuration to load
 | |
| <2> Inject the configuration
 | |
| <3> Create the `WebTestClient`
 | |
| ======
 | |
| 
 | |
| 
 | |
| 
 | |
| [[webtestclient-fn-config]]
 | |
| === Bind to Router Function
 | |
| 
 | |
| This setup allows you to test <<web-reactive.adoc#webflux-fn, functional endpoints>> via
 | |
| mock request and response objects, without a running server.
 | |
| 
 | |
| For WebFlux, use the following which delegates to `RouterFunctions.toWebHandler` to
 | |
| create a server setup to handle requests:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,indent=0,subs="verbatim,quotes",role="primary"]
 | |
| ----
 | |
| 	RouterFunction<?> route = ...
 | |
| 	client = WebTestClient.bindToRouterFunction(route).build();
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
 | |
| ----
 | |
| 	val route: RouterFunction<*> = ...
 | |
| 	val client = WebTestClient.bindToRouterFunction(route).build()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| For Spring MVC there are currently no options to test
 | |
| xref:web/webmvc-functional.adoc[WebMvc functional endpoints].
 | |
| 
 | |
| 
 | |
| 
 | |
| [[webtestclient-server-config]]
 | |
| === Bind to Server
 | |
| 
 | |
| This setup connects to a running server to perform full, end-to-end HTTP tests:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,indent=0,subs="verbatim,quotes",role="primary"]
 | |
| ----
 | |
| 	client = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build();
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
 | |
| ----
 | |
| 	client = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| 
 | |
| 
 | |
| [[webtestclient-client-config]]
 | |
| === Client Config
 | |
| 
 | |
| In addition to the server setup options described earlier, you can also configure client
 | |
| options, including base URL, default headers, client filters, and others. These options
 | |
| are readily available following `bindToServer()`. For all other configuration options,
 | |
| you need to use `configureClient()` to transition from server to client configuration, as
 | |
| follows:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,indent=0,subs="verbatim,quotes",role="primary"]
 | |
| ----
 | |
| 	client = WebTestClient.bindToController(new TestController())
 | |
| 			.configureClient()
 | |
| 			.baseUrl("/test")
 | |
| 			.build();
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
 | |
| ----
 | |
| 	client = WebTestClient.bindToController(TestController())
 | |
| 			.configureClient()
 | |
| 			.baseUrl("/test")
 | |
| 			.build()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| [[webtestclient-tests]]
 | |
| == Writing Tests
 | |
| 
 | |
| `WebTestClient` provides an API identical to xref:web/webflux-webclient.adoc[WebClient]
 | |
| up to the point of performing a request by using `exchange()`. See the
 | |
| xref:web/webflux-webclient/client-body.adoc[WebClient] documentation for examples on how to
 | |
| prepare a request with any content including form data, multipart data, and more.
 | |
| 
 | |
| 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 following:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,indent=0,subs="verbatim,quotes",role="primary"]
 | |
| ----
 | |
| 	client.get().uri("/persons/1")
 | |
| 		.accept(MediaType.APPLICATION_JSON)
 | |
| 		.exchange()
 | |
| 		.expectStatus().isOk()
 | |
| 		.expectHeader().contentType(MediaType.APPLICATION_JSON);
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
 | |
| ----
 | |
| 	client.get().uri("/persons/1")
 | |
| 		.accept(MediaType.APPLICATION_JSON)
 | |
| 		.exchange()
 | |
| 		.expectStatus().isOk()
 | |
| 		.expectHeader().contentType(MediaType.APPLICATION_JSON)
 | |
| ----
 | |
| ======
 | |
| 
 | |
| If you would like for all expectations to be asserted even if one of them fails, you can
 | |
| use `expectAll(..)` instead of multiple chained `expect*(..)` calls. This feature is
 | |
| similar to the _soft assertions_ support in AssertJ and the `assertAll()` support in
 | |
| JUnit Jupiter.
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,indent=0,subs="verbatim,quotes",role="primary"]
 | |
| ----
 | |
| 	client.get().uri("/persons/1")
 | |
| 		.accept(MediaType.APPLICATION_JSON)
 | |
| 		.exchange()
 | |
| 		.expectAll(
 | |
| 			spec -> spec.expectStatus().isOk(),
 | |
| 			spec -> spec.expectHeader().contentType(MediaType.APPLICATION_JSON)
 | |
| 		);
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
 | |
| ----
 | |
| 	client.get().uri("/persons/1")
 | |
| 		.accept(MediaType.APPLICATION_JSON)
 | |
| 		.exchange()
 | |
| 		.expectAll(
 | |
| 			{ spec -> spec.expectStatus().isOk() },
 | |
| 			{ spec -> spec.expectHeader().contentType(MediaType.APPLICATION_JSON) }
 | |
| 		)
 | |
| ----
 | |
| ======
 | |
| 
 | |
| You can then choose to decode the response body through one of the following:
 | |
| 
 | |
| * `expectBody(Class<T>)`: Decode to single object.
 | |
| * `expectBodyList(Class<T>)`: Decode and collect objects to `List<T>`.
 | |
| * `expectBody()`: Decode to `byte[]` for xref:testing/webtestclient.adoc#webtestclient-json[JSON Content] or an empty body.
 | |
| 
 | |
| And perform assertions on the resulting higher level Object(s):
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,indent=0,subs="verbatim,quotes",role="primary"]
 | |
| ----
 | |
| 	client.get().uri("/persons")
 | |
| 			.exchange()
 | |
| 			.expectStatus().isOk()
 | |
| 			.expectBodyList(Person.class).hasSize(3).contains(person);
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
 | |
| ----
 | |
| 	import org.springframework.test.web.reactive.server.expectBodyList
 | |
| 
 | |
| 	client.get().uri("/persons")
 | |
| 			.exchange()
 | |
| 			.expectStatus().isOk()
 | |
| 			.expectBodyList<Person>().hasSize(3).contains(person)
 | |
| ----
 | |
| ======
 | |
| 
 | |
| If the built-in assertions are insufficient, you can consume the object instead and
 | |
| perform any other assertions:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,indent=0,subs="verbatim,quotes",role="primary"]
 | |
| ----
 | |
|     import org.springframework.test.web.reactive.server.expectBody
 | |
| 
 | |
| 	client.get().uri("/persons/1")
 | |
| 			.exchange()
 | |
| 			.expectStatus().isOk()
 | |
| 			.expectBody(Person.class)
 | |
| 			.consumeWith(result -> {
 | |
| 				// custom assertions (e.g. AssertJ)...
 | |
| 			});
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
 | |
| ----
 | |
| 	client.get().uri("/persons/1")
 | |
| 			.exchange()
 | |
| 			.expectStatus().isOk()
 | |
| 			.expectBody<Person>()
 | |
| 			.consumeWith {
 | |
| 				// custom assertions (e.g. AssertJ)...
 | |
| 			}
 | |
| ----
 | |
| ======
 | |
| 
 | |
| Or you can exit the workflow and obtain an `EntityExchangeResult`:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,indent=0,subs="verbatim,quotes",role="primary"]
 | |
| ----
 | |
| 	EntityExchangeResult<Person> result = client.get().uri("/persons/1")
 | |
| 			.exchange()
 | |
| 			.expectStatus().isOk()
 | |
| 			.expectBody(Person.class)
 | |
| 			.returnResult();
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
 | |
| ----
 | |
| 	import org.springframework.test.web.reactive.server.expectBody
 | |
| 
 | |
| 	val result = client.get().uri("/persons/1")
 | |
| 			.exchange()
 | |
| 			.expectStatus().isOk
 | |
| 			.expectBody<Person>()
 | |
| 			.returnResult()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| TIP: When you need to decode to a target type with generics, look for the overloaded methods
 | |
| that accept
 | |
| {spring-framework-api}/core/ParameterizedTypeReference.html[`ParameterizedTypeReference`]
 | |
| instead of `Class<T>`.
 | |
| 
 | |
| 
 | |
| 
 | |
| [[webtestclient-no-content]]
 | |
| === No Content
 | |
| 
 | |
| If the response is not expected to have content, you can assert that as follows:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,indent=0,subs="verbatim,quotes",role="primary"]
 | |
| ----
 | |
| 	client.post().uri("/persons")
 | |
| 			.body(personMono, Person.class)
 | |
| 			.exchange()
 | |
| 			.expectStatus().isCreated()
 | |
| 			.expectBody().isEmpty();
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
 | |
| ----
 | |
| 	client.post().uri("/persons")
 | |
| 			.bodyValue(person)
 | |
| 			.exchange()
 | |
| 			.expectStatus().isCreated()
 | |
| 			.expectBody().isEmpty()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| If you want to ignore the response content, the following releases the content without
 | |
| any assertions:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,indent=0,subs="verbatim,quotes",role="primary"]
 | |
| ----
 | |
| 	client.get().uri("/persons/123")
 | |
| 			.exchange()
 | |
| 			.expectStatus().isNotFound()
 | |
| 			.expectBody(Void.class);
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
 | |
| ----
 | |
| 	client.get().uri("/persons/123")
 | |
| 			.exchange()
 | |
| 			.expectStatus().isNotFound
 | |
| 			.expectBody<Unit>()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| 
 | |
| 
 | |
| [[webtestclient-json]]
 | |
| === JSON Content
 | |
| 
 | |
| 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]:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,indent=0,subs="verbatim,quotes",role="primary"]
 | |
| ----
 | |
| 	client.get().uri("/persons/1")
 | |
| 			.exchange()
 | |
| 			.expectStatus().isOk()
 | |
| 			.expectBody()
 | |
| 			.json("{\"name\":\"Jane\"}")
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
 | |
| ----
 | |
| 	client.get().uri("/persons/1")
 | |
| 			.exchange()
 | |
| 			.expectStatus().isOk()
 | |
| 			.expectBody()
 | |
| 			.json("{\"name\":\"Jane\"}")
 | |
| ----
 | |
| ======
 | |
| 
 | |
| To verify JSON content with https://github.com/jayway/JsonPath[JSONPath]:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,indent=0,subs="verbatim,quotes",role="primary"]
 | |
| ----
 | |
| 	client.get().uri("/persons")
 | |
| 			.exchange()
 | |
| 			.expectStatus().isOk()
 | |
| 			.expectBody()
 | |
| 			.jsonPath("$[0].name").isEqualTo("Jane")
 | |
| 			.jsonPath("$[1].name").isEqualTo("Jason");
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
 | |
| ----
 | |
| 	client.get().uri("/persons")
 | |
| 			.exchange()
 | |
| 			.expectStatus().isOk()
 | |
| 			.expectBody()
 | |
| 			.jsonPath("$[0].name").isEqualTo("Jane")
 | |
| 			.jsonPath("$[1].name").isEqualTo("Jason")
 | |
| ----
 | |
| ======
 | |
| 
 | |
| 
 | |
| 
 | |
| [[webtestclient-stream]]
 | |
| === Streaming Responses
 | |
| 
 | |
| 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`:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,indent=0,subs="verbatim,quotes",role="primary"]
 | |
| ----
 | |
| 	FluxExchangeResult<MyEvent> result = client.get().uri("/events")
 | |
| 			.accept(TEXT_EVENT_STREAM)
 | |
| 			.exchange()
 | |
| 			.expectStatus().isOk()
 | |
| 			.returnResult(MyEvent.class);
 | |
| 
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
 | |
| ----
 | |
| 	import org.springframework.test.web.reactive.server.returnResult
 | |
| 
 | |
| 	val result = client.get().uri("/events")
 | |
| 			.accept(TEXT_EVENT_STREAM)
 | |
| 			.exchange()
 | |
| 			.expectStatus().isOk()
 | |
| 			.returnResult<MyEvent>()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| Now you're ready to consume the response stream with `StepVerifier` from `reactor-test`:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,indent=0,subs="verbatim,quotes",role="primary"]
 | |
| ----
 | |
| 	Flux<Event> eventFlux = result.getResponseBody();
 | |
| 
 | |
| 	StepVerifier.create(eventFlux)
 | |
| 			.expectNext(person)
 | |
| 			.expectNextCount(4)
 | |
| 			.consumeNextWith(p -> ...)
 | |
| 			.thenCancel()
 | |
| 			.verify();
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
 | |
| ----
 | |
| 	val eventFlux = result.getResponseBody()
 | |
| 
 | |
| 	StepVerifier.create(eventFlux)
 | |
| 			.expectNext(person)
 | |
| 			.expectNextCount(4)
 | |
| 			.consumeNextWith { p -> ... }
 | |
| 			.thenCancel()
 | |
| 			.verify()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| 
 | |
| [[webtestclient-mockmvc]]
 | |
| === MockMvc Assertions
 | |
| 
 | |
| `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:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,indent=0,subs="verbatim,quotes",role="primary"]
 | |
| ----
 | |
| 	// For a response with a body
 | |
| 	EntityExchangeResult<Person> result = client.get().uri("/persons/1")
 | |
| 			.exchange()
 | |
| 			.expectStatus().isOk()
 | |
| 			.expectBody(Person.class)
 | |
| 			.returnResult();
 | |
| 
 | |
| 	// For a response without a body
 | |
| 	EntityExchangeResult<Void> result = client.get().uri("/path")
 | |
| 			.exchange()
 | |
| 			.expectBody().isEmpty();
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
 | |
| ----
 | |
| 	// For a response with a body
 | |
| 	val result = client.get().uri("/persons/1")
 | |
| 			.exchange()
 | |
| 			.expectStatus().isOk()
 | |
| 			.expectBody<Person>()
 | |
| 			.returnResult()
 | |
| 
 | |
| 	// For a response without a body
 | |
| 	val result = client.get().uri("/path")
 | |
| 			.exchange()
 | |
| 			.expectBody().isEmpty()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| Then switch to MockMvc server response assertions:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,indent=0,subs="verbatim,quotes",role="primary"]
 | |
| ----
 | |
| 	MockMvcWebTestClient.resultActionsFor(result)
 | |
| 			.andExpect(model().attribute("integer", 3))
 | |
| 			.andExpect(model().attribute("string", "a string value"));
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
 | |
| ----
 | |
| 	MockMvcWebTestClient.resultActionsFor(result)
 | |
| 			.andExpect(model().attribute("integer", 3))
 | |
| 			.andExpect(model().attribute("string", "a string value"));
 | |
| ----
 | |
| ======
 |