2017-10-03 21:56:13 +08:00
|
|
|
[[webtestclient]]
|
|
|
|
= WebTestClient
|
|
|
|
|
2020-09-01 04:14:45 +08:00
|
|
|
`WebTestClient` is an HTTP client designed for testing server applications. It wraps
|
2023-04-19 23:26:17 +08:00
|
|
|
Spring's xref:web/webflux-webclient.adoc[WebClient] and uses it to perform requests
|
2020-09-01 04:14:45 +08:00
|
|
|
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.
|
2018-02-15 06:32:24 +08:00
|
|
|
|
2017-10-03 21:56:13 +08:00
|
|
|
|
2017-10-19 02:24:17 +08:00
|
|
|
|
2018-10-25 21:15:58 +08:00
|
|
|
|
2017-10-03 21:56:13 +08:00
|
|
|
[[webtestclient-setup]]
|
|
|
|
== Setup
|
|
|
|
|
2020-09-01 04:14:45 +08:00
|
|
|
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.
|
2017-10-03 21:56:13 +08:00
|
|
|
|
|
|
|
|
2017-10-19 02:24:17 +08:00
|
|
|
|
2017-10-03 21:56:13 +08:00
|
|
|
[[webtestclient-controller-config]]
|
2018-08-30 23:29:17 +08:00
|
|
|
=== Bind to Controller
|
2017-10-03 21:56:13 +08:00
|
|
|
|
2020-09-01 04:14:45 +08:00
|
|
|
This setup allows you to test specific controller(s) via mock request and response objects,
|
|
|
|
without a running server.
|
|
|
|
|
2020-09-18 21:33:54 +08:00
|
|
|
For WebFlux applications, use the following which loads infrastructure equivalent to the
|
2023-04-19 23:26:17 +08:00
|
|
|
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]
|
2020-09-18 21:33:54 +08:00
|
|
|
to handle requests:
|
2017-10-03 21:56:13 +08:00
|
|
|
|
2023-04-21 05:21:36 +08:00
|
|
|
[tabs]
|
|
|
|
======
|
|
|
|
Java::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
2017-10-03 21:56:13 +08:00
|
|
|
----
|
2020-09-01 04:14:45 +08:00
|
|
|
WebTestClient client =
|
|
|
|
WebTestClient.bindToController(new TestController()).build();
|
2017-10-03 21:56:13 +08:00
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
|
|
|
|
Kotlin::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
2019-08-31 17:31:16 +08:00
|
|
|
----
|
2020-09-01 04:14:45 +08:00
|
|
|
val client = WebTestClient.bindToController(TestController()).build()
|
2019-08-31 17:31:16 +08:00
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
======
|
2017-10-03 21:56:13 +08:00
|
|
|
|
2020-09-18 21:33:54 +08:00
|
|
|
For Spring MVC, use the following which delegates to the
|
2023-11-21 22:59:24 +08:00
|
|
|
{spring-framework-api}/test/web/servlet/setup/StandaloneMockMvcBuilder.html[StandaloneMockMvcBuilder]
|
2023-04-19 23:26:17 +08:00
|
|
|
to load infrastructure equivalent to the xref:web/webmvc/mvc-config.adoc[WebMvc Java config],
|
2020-09-01 15:56:57 +08:00
|
|
|
registers the given controller(s), and creates an instance of
|
2024-06-21 18:50:36 +08:00
|
|
|
xref:testing/mockmvc.adoc[MockMvc] to handle requests:
|
2017-10-03 21:56:13 +08:00
|
|
|
|
2023-04-21 05:21:36 +08:00
|
|
|
[tabs]
|
|
|
|
======
|
|
|
|
Java::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
2017-10-03 21:56:13 +08:00
|
|
|
----
|
2020-09-01 04:14:45 +08:00
|
|
|
WebTestClient client =
|
|
|
|
MockMvcWebTestClient.bindToController(new TestController()).build();
|
2017-10-03 21:56:13 +08:00
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
|
|
|
|
Kotlin::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
2019-08-31 17:31:16 +08:00
|
|
|
----
|
2020-09-01 04:14:45 +08:00
|
|
|
val client = MockMvcWebTestClient.bindToController(TestController()).build()
|
2019-08-31 17:31:16 +08:00
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
======
|
2017-10-03 21:56:13 +08:00
|
|
|
|
|
|
|
|
2017-10-19 02:24:17 +08:00
|
|
|
|
2017-10-03 21:56:13 +08:00
|
|
|
[[webtestclient-context-config]]
|
2018-08-30 23:29:17 +08:00
|
|
|
=== Bind to `ApplicationContext`
|
2017-10-03 21:56:13 +08:00
|
|
|
|
2020-09-01 15:56:57 +08:00
|
|
|
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.
|
2020-09-01 04:14:45 +08:00
|
|
|
|
2020-09-18 21:33:54 +08:00
|
|
|
For WebFlux, use the following where the Spring `ApplicationContext` is passed to
|
2023-11-21 22:59:24 +08:00
|
|
|
{spring-framework-api}/web/server/adapter/WebHttpHandlerBuilder.html#applicationContext-org.springframework.context.ApplicationContext-[WebHttpHandlerBuilder]
|
2023-04-19 23:26:17 +08:00
|
|
|
to create the xref:web/webflux/reactive-spring.adoc#webflux-web-handler-api[WebHandler chain] to handle
|
2020-09-18 21:33:54 +08:00
|
|
|
requests:
|
2017-10-03 21:56:13 +08:00
|
|
|
|
2023-04-21 05:21:36 +08:00
|
|
|
[tabs]
|
|
|
|
======
|
|
|
|
Java::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
2017-10-03 21:56:13 +08:00
|
|
|
----
|
2019-08-31 17:31:16 +08:00
|
|
|
@SpringJUnitConfig(WebConfig.class) // <1>
|
2019-08-31 20:47:49 +08:00
|
|
|
class MyTests {
|
2017-11-21 05:28:00 +08:00
|
|
|
|
2019-08-31 20:47:49 +08:00
|
|
|
WebTestClient client;
|
2017-11-21 05:28:00 +08:00
|
|
|
|
2019-08-31 17:31:16 +08:00
|
|
|
@BeforeEach
|
2019-08-31 20:47:49 +08:00
|
|
|
void setUp(ApplicationContext context) { // <2>
|
2017-11-21 05:28:00 +08:00
|
|
|
client = WebTestClient.bindToApplicationContext(context).build(); // <3>
|
|
|
|
}
|
|
|
|
}
|
2017-10-03 21:56:13 +08:00
|
|
|
----
|
2019-08-31 17:31:16 +08:00
|
|
|
<1> Specify the configuration to load
|
|
|
|
<2> Inject the configuration
|
|
|
|
<3> Create the `WebTestClient`
|
|
|
|
|
2023-05-06 09:19:47 +08:00
|
|
|
Kotlin::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
2019-08-31 17:31:16 +08:00
|
|
|
----
|
|
|
|
@SpringJUnitConfig(WebConfig::class) // <1>
|
|
|
|
class MyTests {
|
2017-10-03 21:56:13 +08:00
|
|
|
|
2019-08-31 17:31:16 +08:00
|
|
|
lateinit var client: WebTestClient
|
|
|
|
|
|
|
|
@BeforeEach
|
2019-08-31 20:47:49 +08:00
|
|
|
fun setUp(context: ApplicationContext) { // <2>
|
2019-08-31 17:31:16 +08:00
|
|
|
client = WebTestClient.bindToApplicationContext(context).build() // <3>
|
|
|
|
}
|
|
|
|
}
|
|
|
|
----
|
2017-10-03 21:56:13 +08:00
|
|
|
<1> Specify the configuration to load
|
|
|
|
<2> Inject the configuration
|
|
|
|
<3> Create the `WebTestClient`
|
2023-05-06 09:19:47 +08:00
|
|
|
======
|
2017-10-03 21:56:13 +08:00
|
|
|
|
2020-09-18 21:33:54 +08:00
|
|
|
For Spring MVC, use the following where the Spring `ApplicationContext` is passed to
|
2023-11-21 22:59:24 +08:00
|
|
|
{spring-framework-api}/test/web/servlet/setup/MockMvcBuilders.html#webAppContextSetup-org.springframework.web.context.WebApplicationContext-[MockMvcBuilders.webAppContextSetup]
|
2024-06-21 18:50:36 +08:00
|
|
|
to create a xref:testing/mockmvc.adoc[MockMvc] instance to handle
|
2020-09-18 21:33:54 +08:00
|
|
|
requests:
|
2020-09-01 04:14:45 +08:00
|
|
|
|
2023-04-21 05:21:36 +08:00
|
|
|
[tabs]
|
|
|
|
======
|
|
|
|
Java::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
2020-09-01 04:14:45 +08:00
|
|
|
----
|
|
|
|
@ExtendWith(SpringExtension.class)
|
|
|
|
@WebAppConfiguration("classpath:META-INF/web-resources") // <1>
|
|
|
|
@ContextHierarchy({
|
|
|
|
@ContextConfiguration(classes = RootConfig.class),
|
|
|
|
@ContextConfiguration(classes = WebConfig.class)
|
|
|
|
})
|
|
|
|
class MyTests {
|
|
|
|
|
|
|
|
@Autowired
|
2020-09-18 21:33:54 +08:00
|
|
|
WebApplicationContext wac; // <2>
|
2020-09-01 04:14:45 +08:00
|
|
|
|
|
|
|
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`
|
|
|
|
|
2023-05-06 09:19:47 +08:00
|
|
|
Kotlin::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
2020-09-01 04:14:45 +08:00
|
|
|
----
|
|
|
|
@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>
|
2020-09-18 21:33:54 +08:00
|
|
|
client = MockMvcWebTestClient.bindToApplicationContext(wac).build() // <3>
|
2020-09-01 04:14:45 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
----
|
|
|
|
<1> Specify the configuration to load
|
|
|
|
<2> Inject the configuration
|
|
|
|
<3> Create the `WebTestClient`
|
2023-05-06 09:19:47 +08:00
|
|
|
======
|
2020-09-01 04:14:45 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[[webtestclient-fn-config]]
|
|
|
|
=== Bind to Router Function
|
|
|
|
|
2020-09-18 21:33:54 +08:00
|
|
|
This setup allows you to test <<web-reactive.adoc#webflux-fn, functional endpoints>> via
|
|
|
|
mock request and response objects, without a running server.
|
2020-09-01 04:14:45 +08:00
|
|
|
|
2020-09-18 21:33:54 +08:00
|
|
|
For WebFlux, use the following which delegates to `RouterFunctions.toWebHandler` to
|
|
|
|
create a server setup to handle requests:
|
2020-09-01 04:14:45 +08:00
|
|
|
|
2023-04-21 05:21:36 +08:00
|
|
|
[tabs]
|
|
|
|
======
|
|
|
|
Java::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
2020-09-01 04:14:45 +08:00
|
|
|
----
|
|
|
|
RouterFunction<?> route = ...
|
|
|
|
client = WebTestClient.bindToRouterFunction(route).build();
|
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
|
|
|
|
Kotlin::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
2020-09-01 04:14:45 +08:00
|
|
|
----
|
|
|
|
val route: RouterFunction<*> = ...
|
|
|
|
val client = WebTestClient.bindToRouterFunction(route).build()
|
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
======
|
2020-09-01 04:14:45 +08:00
|
|
|
|
2020-09-18 21:33:54 +08:00
|
|
|
For Spring MVC there are currently no options to test
|
2023-04-19 23:26:17 +08:00
|
|
|
xref:web/webmvc-functional.adoc[WebMvc functional endpoints].
|
2017-10-03 21:56:13 +08:00
|
|
|
|
|
|
|
|
2017-10-19 02:24:17 +08:00
|
|
|
|
2017-10-03 21:56:13 +08:00
|
|
|
[[webtestclient-server-config]]
|
2018-08-30 23:29:17 +08:00
|
|
|
=== Bind to Server
|
2017-10-03 21:56:13 +08:00
|
|
|
|
2020-09-01 15:56:57 +08:00
|
|
|
This setup connects to a running server to perform full, end-to-end HTTP tests:
|
2017-10-03 21:56:13 +08:00
|
|
|
|
2023-04-21 05:21:36 +08:00
|
|
|
[tabs]
|
|
|
|
======
|
|
|
|
Java::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
2017-10-03 21:56:13 +08:00
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
client = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build();
|
2017-10-03 21:56:13 +08:00
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
|
|
|
|
Kotlin::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
2019-08-31 17:31:16 +08:00
|
|
|
----
|
|
|
|
client = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build()
|
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
======
|
2018-08-30 23:29:17 +08:00
|
|
|
|
2017-10-03 21:56:13 +08:00
|
|
|
|
|
|
|
|
|
|
|
[[webtestclient-client-config]]
|
2020-09-01 04:14:45 +08:00
|
|
|
=== Client Config
|
2017-10-03 21:56:13 +08:00
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
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
|
2020-09-18 21:33:54 +08:00
|
|
|
are readily available following `bindToServer()`. For all other configuration options,
|
|
|
|
you need to use `configureClient()` to transition from server to client configuration, as
|
|
|
|
follows:
|
2017-10-03 21:56:13 +08:00
|
|
|
|
2023-04-21 05:21:36 +08:00
|
|
|
[tabs]
|
|
|
|
======
|
|
|
|
Java::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
2017-10-03 21:56:13 +08:00
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
client = WebTestClient.bindToController(new TestController())
|
|
|
|
.configureClient()
|
|
|
|
.baseUrl("/test")
|
|
|
|
.build();
|
2017-10-03 21:56:13 +08:00
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
|
|
|
|
Kotlin::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
2019-08-31 17:31:16 +08:00
|
|
|
----
|
|
|
|
client = WebTestClient.bindToController(TestController())
|
|
|
|
.configureClient()
|
|
|
|
.baseUrl("/test")
|
|
|
|
.build()
|
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
======
|
2017-10-03 21:56:13 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
2018-10-25 21:15:58 +08:00
|
|
|
|
2017-10-03 21:56:13 +08:00
|
|
|
[[webtestclient-tests]]
|
2018-08-30 23:29:17 +08:00
|
|
|
== Writing Tests
|
2017-10-03 21:56:13 +08:00
|
|
|
|
2023-04-19 23:26:17 +08:00
|
|
|
`WebTestClient` provides an API identical to xref:web/webflux-webclient.adoc[WebClient]
|
2020-09-01 15:56:57 +08:00
|
|
|
up to the point of performing a request by using `exchange()`. See the
|
2023-04-19 23:26:17 +08:00
|
|
|
xref:web/webflux-webclient/client-body.adoc[WebClient] documentation for examples on how to
|
2020-09-01 15:56:57 +08:00
|
|
|
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.
|
2017-10-03 21:56:13 +08:00
|
|
|
|
2020-09-18 21:33:54 +08:00
|
|
|
To assert the response status and headers, use the following:
|
2017-10-03 21:56:13 +08:00
|
|
|
|
2023-04-21 05:21:36 +08:00
|
|
|
[tabs]
|
|
|
|
======
|
|
|
|
Java::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
2019-08-31 17:31:16 +08:00
|
|
|
----
|
|
|
|
client.get().uri("/persons/1")
|
2021-08-24 00:59:41 +08:00
|
|
|
.accept(MediaType.APPLICATION_JSON)
|
|
|
|
.exchange()
|
|
|
|
.expectStatus().isOk()
|
|
|
|
.expectHeader().contentType(MediaType.APPLICATION_JSON);
|
2019-08-31 17:31:16 +08:00
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
|
|
|
|
Kotlin::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
2017-10-03 21:56:13 +08:00
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
client.get().uri("/persons/1")
|
2021-08-24 00:59:41 +08:00
|
|
|
.accept(MediaType.APPLICATION_JSON)
|
|
|
|
.exchange()
|
|
|
|
.expectStatus().isOk()
|
|
|
|
.expectHeader().contentType(MediaType.APPLICATION_JSON)
|
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
======
|
2021-08-24 00:59:41 +08:00
|
|
|
|
|
|
|
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.
|
|
|
|
|
2023-04-21 05:21:36 +08:00
|
|
|
[tabs]
|
|
|
|
======
|
|
|
|
Java::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
2021-08-24 00:59:41 +08:00
|
|
|
----
|
|
|
|
client.get().uri("/persons/1")
|
|
|
|
.accept(MediaType.APPLICATION_JSON)
|
|
|
|
.exchange()
|
|
|
|
.expectAll(
|
|
|
|
spec -> spec.expectStatus().isOk(),
|
|
|
|
spec -> spec.expectHeader().contentType(MediaType.APPLICATION_JSON)
|
|
|
|
);
|
2017-10-03 21:56:13 +08:00
|
|
|
----
|
2024-08-08 06:37:26 +08:00
|
|
|
|
|
|
|
Kotlin::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
2024-08-08 06:37:26 +08:00
|
|
|
----
|
|
|
|
client.get().uri("/persons/1")
|
|
|
|
.accept(MediaType.APPLICATION_JSON)
|
|
|
|
.exchange()
|
|
|
|
.expectAll(
|
|
|
|
{ spec -> spec.expectStatus().isOk() },
|
|
|
|
{ spec -> spec.expectHeader().contentType(MediaType.APPLICATION_JSON) }
|
|
|
|
)
|
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
======
|
2017-10-03 21:56:13 +08:00
|
|
|
|
2020-09-01 15:56:57 +08:00
|
|
|
You can then choose to decode the response body through one of the following:
|
2017-10-03 21:56:13 +08:00
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
* `expectBody(Class<T>)`: Decode to single object.
|
|
|
|
* `expectBodyList(Class<T>)`: Decode and collect objects to `List<T>`.
|
2023-04-19 23:26:17 +08:00
|
|
|
* `expectBody()`: Decode to `byte[]` for xref:testing/webtestclient.adoc#webtestclient-json[JSON Content] or an empty body.
|
2017-10-03 21:56:13 +08:00
|
|
|
|
2020-09-01 15:56:57 +08:00
|
|
|
And perform assertions on the resulting higher level Object(s):
|
2017-10-03 21:56:13 +08:00
|
|
|
|
2023-04-21 05:21:36 +08:00
|
|
|
[tabs]
|
|
|
|
======
|
|
|
|
Java::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
2017-10-03 21:56:13 +08:00
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
client.get().uri("/persons")
|
|
|
|
.exchange()
|
|
|
|
.expectStatus().isOk()
|
|
|
|
.expectBodyList(Person.class).hasSize(3).contains(person);
|
2017-10-03 21:56:13 +08:00
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
|
|
|
|
Kotlin::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
2019-08-31 17:31:16 +08:00
|
|
|
----
|
|
|
|
import org.springframework.test.web.reactive.server.expectBodyList
|
|
|
|
|
|
|
|
client.get().uri("/persons")
|
|
|
|
.exchange()
|
|
|
|
.expectStatus().isOk()
|
|
|
|
.expectBodyList<Person>().hasSize(3).contains(person)
|
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
======
|
2017-10-03 21:56:13 +08:00
|
|
|
|
2020-09-01 15:56:57 +08:00
|
|
|
If the built-in assertions are insufficient, you can consume the object instead and
|
|
|
|
perform any other assertions:
|
2017-10-03 21:56:13 +08:00
|
|
|
|
2023-04-21 05:21:36 +08:00
|
|
|
[tabs]
|
|
|
|
======
|
|
|
|
Java::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
2017-10-03 21:56:13 +08:00
|
|
|
----
|
2019-08-31 17:31:16 +08:00
|
|
|
import org.springframework.test.web.reactive.server.expectBody
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
client.get().uri("/persons/1")
|
|
|
|
.exchange()
|
|
|
|
.expectStatus().isOk()
|
|
|
|
.expectBody(Person.class)
|
|
|
|
.consumeWith(result -> {
|
2024-09-26 20:03:46 +08:00
|
|
|
// custom assertions (for example, AssertJ)...
|
2017-11-21 05:28:00 +08:00
|
|
|
});
|
2017-10-03 21:56:13 +08:00
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
|
|
|
|
Kotlin::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
2019-08-31 17:31:16 +08:00
|
|
|
----
|
|
|
|
client.get().uri("/persons/1")
|
|
|
|
.exchange()
|
|
|
|
.expectStatus().isOk()
|
|
|
|
.expectBody<Person>()
|
|
|
|
.consumeWith {
|
2024-09-26 20:03:46 +08:00
|
|
|
// custom assertions (for example, AssertJ)...
|
2019-08-31 17:31:16 +08:00
|
|
|
}
|
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
======
|
2017-10-03 21:56:13 +08:00
|
|
|
|
2020-09-01 15:56:57 +08:00
|
|
|
Or you can exit the workflow and obtain an `EntityExchangeResult`:
|
2017-10-03 21:56:13 +08:00
|
|
|
|
2023-04-21 05:21:36 +08:00
|
|
|
[tabs]
|
|
|
|
======
|
|
|
|
Java::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
2017-10-03 21:56:13 +08:00
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
EntityExchangeResult<Person> result = client.get().uri("/persons/1")
|
|
|
|
.exchange()
|
|
|
|
.expectStatus().isOk()
|
|
|
|
.expectBody(Person.class)
|
|
|
|
.returnResult();
|
2017-10-03 21:56:13 +08:00
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
|
|
|
|
Kotlin::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
2019-08-31 17:31:16 +08:00
|
|
|
----
|
|
|
|
import org.springframework.test.web.reactive.server.expectBody
|
|
|
|
|
|
|
|
val result = client.get().uri("/persons/1")
|
|
|
|
.exchange()
|
|
|
|
.expectStatus().isOk
|
|
|
|
.expectBody<Person>()
|
|
|
|
.returnResult()
|
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
======
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
|
|
TIP: When you need to decode to a target type with generics, look for the overloaded methods
|
2017-10-03 21:56:13 +08:00
|
|
|
that accept
|
2023-11-21 22:59:24 +08:00
|
|
|
{spring-framework-api}/core/ParameterizedTypeReference.html[`ParameterizedTypeReference`]
|
2017-10-03 21:56:13 +08:00
|
|
|
instead of `Class<T>`.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
2017-10-03 21:56:13 +08:00
|
|
|
|
|
|
|
|
2017-10-05 02:58:11 +08:00
|
|
|
[[webtestclient-no-content]]
|
2018-08-30 23:29:17 +08:00
|
|
|
=== No Content
|
2017-10-05 02:58:11 +08:00
|
|
|
|
2020-09-01 15:56:57 +08:00
|
|
|
If the response is not expected to have content, you can assert that as follows:
|
2017-10-05 02:58:11 +08:00
|
|
|
|
2023-04-21 05:21:36 +08:00
|
|
|
[tabs]
|
|
|
|
======
|
|
|
|
Java::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
2017-10-05 02:58:11 +08:00
|
|
|
----
|
2020-09-01 15:56:57 +08:00
|
|
|
client.post().uri("/persons")
|
|
|
|
.body(personMono, Person.class)
|
2017-11-21 05:28:00 +08:00
|
|
|
.exchange()
|
2020-09-01 15:56:57 +08:00
|
|
|
.expectStatus().isCreated()
|
|
|
|
.expectBody().isEmpty();
|
2017-10-05 02:58:11 +08:00
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
|
|
|
|
Kotlin::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
2019-08-31 17:31:16 +08:00
|
|
|
----
|
2020-09-01 15:56:57 +08:00
|
|
|
client.post().uri("/persons")
|
|
|
|
.bodyValue(person)
|
2019-08-31 17:31:16 +08:00
|
|
|
.exchange()
|
2020-09-01 15:56:57 +08:00
|
|
|
.expectStatus().isCreated()
|
|
|
|
.expectBody().isEmpty()
|
2019-08-31 17:31:16 +08:00
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
======
|
2017-10-05 02:58:11 +08:00
|
|
|
|
2020-09-01 15:56:57 +08:00
|
|
|
If you want to ignore the response content, the following releases the content without
|
|
|
|
any assertions:
|
2017-10-05 02:58:11 +08:00
|
|
|
|
2023-04-21 05:21:36 +08:00
|
|
|
[tabs]
|
|
|
|
======
|
|
|
|
Java::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
2017-10-05 02:58:11 +08:00
|
|
|
----
|
2020-09-01 15:56:57 +08:00
|
|
|
client.get().uri("/persons/123")
|
2017-11-21 05:28:00 +08:00
|
|
|
.exchange()
|
2020-09-01 15:56:57 +08:00
|
|
|
.expectStatus().isNotFound()
|
|
|
|
.expectBody(Void.class);
|
2017-10-05 02:58:11 +08:00
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
|
|
|
|
Kotlin::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
2019-08-31 17:31:16 +08:00
|
|
|
----
|
2020-09-01 15:56:57 +08:00
|
|
|
client.get().uri("/persons/123")
|
2019-08-31 20:47:49 +08:00
|
|
|
.exchange()
|
2020-09-01 15:56:57 +08:00
|
|
|
.expectStatus().isNotFound
|
|
|
|
.expectBody<Unit>()
|
2019-08-31 17:31:16 +08:00
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
======
|
2017-10-05 02:58:11 +08:00
|
|
|
|
|
|
|
|
2020-09-01 15:56:57 +08:00
|
|
|
|
2017-10-03 21:56:13 +08:00
|
|
|
[[webtestclient-json]]
|
2018-08-30 23:29:17 +08:00
|
|
|
=== JSON Content
|
2017-10-03 21:56:13 +08:00
|
|
|
|
2020-09-01 15:56:57 +08:00
|
|
|
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]:
|
2017-10-03 21:56:13 +08:00
|
|
|
|
2023-04-21 05:21:36 +08:00
|
|
|
[tabs]
|
|
|
|
======
|
|
|
|
Java::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
2019-08-31 17:31:16 +08:00
|
|
|
----
|
|
|
|
client.get().uri("/persons/1")
|
|
|
|
.exchange()
|
|
|
|
.expectStatus().isOk()
|
|
|
|
.expectBody()
|
|
|
|
.json("{\"name\":\"Jane\"}")
|
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
|
|
|
|
Kotlin::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
2017-10-03 21:56:13 +08:00
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
client.get().uri("/persons/1")
|
|
|
|
.exchange()
|
|
|
|
.expectStatus().isOk()
|
|
|
|
.expectBody()
|
|
|
|
.json("{\"name\":\"Jane\"}")
|
2017-10-03 21:56:13 +08:00
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
======
|
2017-10-03 21:56:13 +08:00
|
|
|
|
2020-09-01 15:56:57 +08:00
|
|
|
To verify JSON content with https://github.com/jayway/JsonPath[JSONPath]:
|
2017-10-03 21:56:13 +08:00
|
|
|
|
2023-04-21 05:21:36 +08:00
|
|
|
[tabs]
|
|
|
|
======
|
|
|
|
Java::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
2017-10-03 21:56:13 +08:00
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
client.get().uri("/persons")
|
|
|
|
.exchange()
|
|
|
|
.expectStatus().isOk()
|
|
|
|
.expectBody()
|
|
|
|
.jsonPath("$[0].name").isEqualTo("Jane")
|
|
|
|
.jsonPath("$[1].name").isEqualTo("Jason");
|
2017-10-03 21:56:13 +08:00
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
|
|
|
|
Kotlin::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
2019-08-31 17:31:16 +08:00
|
|
|
----
|
|
|
|
client.get().uri("/persons")
|
|
|
|
.exchange()
|
|
|
|
.expectStatus().isOk()
|
|
|
|
.expectBody()
|
|
|
|
.jsonPath("$[0].name").isEqualTo("Jane")
|
|
|
|
.jsonPath("$[1].name").isEqualTo("Jason")
|
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
======
|
2018-08-30 23:29:17 +08:00
|
|
|
|
2017-10-03 21:56:13 +08:00
|
|
|
|
|
|
|
|
|
|
|
[[webtestclient-stream]]
|
2018-08-30 23:29:17 +08:00
|
|
|
=== Streaming Responses
|
2017-10-03 21:56:13 +08:00
|
|
|
|
2020-09-01 15:56:57 +08:00
|
|
|
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`:
|
2017-10-03 21:56:13 +08:00
|
|
|
|
2023-04-21 05:21:36 +08:00
|
|
|
[tabs]
|
|
|
|
======
|
|
|
|
Java::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
2017-10-03 21:56:13 +08:00
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
FluxExchangeResult<MyEvent> result = client.get().uri("/events")
|
|
|
|
.accept(TEXT_EVENT_STREAM)
|
|
|
|
.exchange()
|
|
|
|
.expectStatus().isOk()
|
|
|
|
.returnResult(MyEvent.class);
|
2017-10-03 21:56:13 +08:00
|
|
|
|
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
|
|
|
|
Kotlin::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
2019-08-31 17:31:16 +08:00
|
|
|
----
|
|
|
|
import org.springframework.test.web.reactive.server.returnResult
|
|
|
|
|
|
|
|
val result = client.get().uri("/events")
|
|
|
|
.accept(TEXT_EVENT_STREAM)
|
|
|
|
.exchange()
|
|
|
|
.expectStatus().isOk()
|
|
|
|
.returnResult<MyEvent>()
|
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
======
|
2017-10-03 21:56:13 +08:00
|
|
|
|
2020-09-01 15:56:57 +08:00
|
|
|
Now you're ready to consume the response stream with `StepVerifier` from `reactor-test`:
|
2017-10-03 21:56:13 +08:00
|
|
|
|
2023-04-21 05:21:36 +08:00
|
|
|
[tabs]
|
|
|
|
======
|
|
|
|
Java::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
2017-10-03 21:56:13 +08:00
|
|
|
----
|
2019-08-31 17:31:16 +08:00
|
|
|
Flux<Event> eventFlux = result.getResponseBody();
|
2017-11-21 05:28:00 +08:00
|
|
|
|
|
|
|
StepVerifier.create(eventFlux)
|
|
|
|
.expectNext(person)
|
|
|
|
.expectNextCount(4)
|
|
|
|
.consumeNextWith(p -> ...)
|
|
|
|
.thenCancel()
|
|
|
|
.verify();
|
2017-10-03 21:56:13 +08:00
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
|
|
|
|
Kotlin::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
2019-08-31 17:31:16 +08:00
|
|
|
----
|
|
|
|
val eventFlux = result.getResponseBody()
|
|
|
|
|
|
|
|
StepVerifier.create(eventFlux)
|
|
|
|
.expectNext(person)
|
|
|
|
.expectNextCount(4)
|
|
|
|
.consumeNextWith { p -> ... }
|
|
|
|
.thenCancel()
|
|
|
|
.verify()
|
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
======
|
2018-08-30 23:29:17 +08:00
|
|
|
|
2017-11-07 05:28:27 +08:00
|
|
|
|
2020-09-01 15:56:57 +08:00
|
|
|
[[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:
|
2017-11-07 05:28:27 +08:00
|
|
|
|
2023-04-21 05:21:36 +08:00
|
|
|
[tabs]
|
|
|
|
======
|
|
|
|
Java::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
2020-09-01 15:56:57 +08:00
|
|
|
----
|
|
|
|
// 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();
|
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
|
|
|
|
Kotlin::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
2020-09-01 15:56:57 +08:00
|
|
|
----
|
|
|
|
// For a response with a body
|
|
|
|
val result = client.get().uri("/persons/1")
|
|
|
|
.exchange()
|
|
|
|
.expectStatus().isOk()
|
2024-05-02 17:07:36 +08:00
|
|
|
.expectBody<Person>()
|
2024-04-30 20:30:39 +08:00
|
|
|
.returnResult()
|
2020-09-01 15:56:57 +08:00
|
|
|
|
|
|
|
// For a response without a body
|
|
|
|
val result = client.get().uri("/path")
|
|
|
|
.exchange()
|
2024-04-30 20:30:39 +08:00
|
|
|
.expectBody().isEmpty()
|
2020-09-01 15:56:57 +08:00
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
======
|
2020-09-01 15:56:57 +08:00
|
|
|
|
|
|
|
Then switch to MockMvc server response assertions:
|
|
|
|
|
2023-04-21 05:21:36 +08:00
|
|
|
[tabs]
|
|
|
|
======
|
|
|
|
Java::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
2020-09-01 15:56:57 +08:00
|
|
|
----
|
|
|
|
MockMvcWebTestClient.resultActionsFor(result)
|
|
|
|
.andExpect(model().attribute("integer", 3))
|
|
|
|
.andExpect(model().attribute("string", "a string value"));
|
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
|
|
|
|
Kotlin::
|
|
|
|
+
|
2024-09-08 23:20:10 +08:00
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
2020-09-01 15:56:57 +08:00
|
|
|
----
|
|
|
|
MockMvcWebTestClient.resultActionsFor(result)
|
|
|
|
.andExpect(model().attribute("integer", 3))
|
|
|
|
.andExpect(model().attribute("string", "a string value"));
|
|
|
|
----
|
2023-04-21 05:21:36 +08:00
|
|
|
======
|