Performing Requests
本部分展示了如何使用“MockMvcTester”执行请求及其与 AssertJ 的集成以验证响应。
This section shows how to use MockMvcTester
to perform requests and its integration
with AssertJ to verify responses.
“MockMvcTester”提供了一个流畅的 API 来组合与 Hamcrest 支持重复使用同一“MockHttpServletRequestBuilder”的请求,不同之处在于不需要导入静态方法。返回的生成器支持 AssertJ,因此将其包装在常规“assertThat()”工厂方法中会触发交换并提供对“MvcTestResult”的专用 Assert 对象的访问权限。
MockMvcTester
provides a fluent API to compose the request that reuses the same
MockHttpServletRequestBuilder
as the Hamcrest support, except that there is no need
to import a static method. The builder that is returned is AssertJ-aware so that
wrapping it in the regular assertThat()
factory method triggers the exchange and
provides access to a dedicated Assert object for MvcTestResult
.
以下是一个简单的示例,它在“/hotels/42”上执行“POST”并将请求配置为指定“Accept”标头:
Here is a simple example that performs a POST
on /hotels/42
and configures the
request to specify an Accept
header:
include-code::./HotelControllerTests[tag=post,indent=0]
AssertJ 通常包含多个“assertThat()”语句来验证交换的不同部分。与其像上面示例中那样使用单个语句,可以使用“exchange()”返回可以在多个“assertThat”语句中使用的“MvcTestResult”:
AssertJ often consists of multiple assertThat()
statements to validate the different
parts of the exchange. Rather than having a single statement as in the case above, you
can use .exchange()
to return a MvcTestResult
that can be used in multiple
assertThat
statements:
include-code::./HotelControllerTests[tag=post-exchange,indent=0]
您可以使用 URI 模板样式指定查询参数,如下例所示:
You can specify query parameters in URI template style, as the following example shows: include-code::./HotelControllerTests[tag=query-parameters,indent=0]
您还可以添加 Servlet 请求参数,表示 query 或 formparameters,如下例所示:
You can also add Servlet request parameters that represent either query or form parameters, as the following example shows: include-code::./HotelControllerTests[tag=parameters,indent=0]
如果应用程序代码依赖于 Servlet 请求参数,并且没有明确检查 querystring(这种情况最常见),您使用哪种选项无关紧要。但请记住,使用 URI 模板提供的查询参数已解码,而通过 param(…)
方法提供的请求参数应已解码。
If application code relies on Servlet request parameters and does not check the query
string explicitly (as is most often the case), it does not matter which option you use.
Keep in mind, however, that query parameters provided with the URI template are decoded
while request parameters provided through the param(…)
method are expected to already
be decoded.
Async
如果异步完成请求的处理,exchange()
将等待请求完成,以便断言的结果实际上是不可变的。默认超时时间为 10 秒,但可以按请求逐个请求控制,如下例所示:
If the processing of the request is done asynchronously, exchange()
waits for
the completion of the request so that the result to assert is effectively immutable.
The default timeout is 10 seconds but it can be controlled on a request-by-request
basis as shown in the following example:
-
Java
-
Kotlin
assertThat(mockMvc.get().uri("/compute").exchange(Duration.ofSeconds(5)))
. // ...
assertThat(mockMvc.get().uri("/compute").exchange(Duration.ofSeconds(5)))
. // ...
如果您希望获取原始结果并自己管理异步请求的生命周期,请使用 asyncExchange
而不是 exchange
。
If you prefer to get the raw result and manage the lifecycle of the asynchronous
request yourself, use asyncExchange
rather than exchange
.
Multipart
您可以执行文件上传请求,内部使用 MockMultipartHttpServletRequest
,以便不会实际解析多部分请求。相反,您必须将其设置为类似于以下示例:
You can perform file upload requests that internally use
MockMultipartHttpServletRequest
so that there is no actual parsing of a multipart
request. Rather, you have to set it up to be similar to the following example:
-
Java
-
Kotlin
assertThat(mockMvc.post().uri("/upload").multipart()
.file("file1.txt", "Hello".getBytes(StandardCharsets.UTF_8))
.file("file2.txt", "World".getBytes(StandardCharsets.UTF_8)))
. // ...
assertThat(mockMvc.post().uri("/upload").multipart()
.file("file1.txt", "Hello".toByteArray(StandardCharsets.UTF_8))
.file("file2.txt", "World".toByteArray(StandardCharsets.UTF_8)))
. // ...
Using Servlet and Context Paths
在大多数情况下,最好将上下文路径和 Servlet 路径从请求 URI 中排除出去。如果您必须使用完整的请求 URI 进行测试,请务必相应地设置 contextPath
和 servletPath
,以便请求映射正常工作,如下例所示:
In most cases, it is preferable to leave the context path and the Servlet path out of the
request URI. If you must test with the full request URI, be sure to set the contextPath
and servletPath
accordingly so that request mappings work, as the following example
shows:
-
Java
-
Kotlin
assertThat(mockMvc.get().uri("/app/main/hotels/{id}", 42)
.contextPath("/app").servletPath("/main"))
. // ...
assertThat(mockMvc.get().uri("/app/main/hotels/{id}", 42)
.contextPath("/app").servletPath("/main"))
. // ...
在前面的示例中,在每个执行的请求中设置 contextPath
和 servletPath
会很麻烦。相反,您可以设置默认请求属性,如下例所示:
In the preceding example, it would be cumbersome to set the contextPath
and
servletPath
with every performed request. Instead, you can set up default request
properties, as the following example shows:
-
Java
-
Kotlin
MockMvcTester mockMvc = MockMvcTester.of(List.of(new HotelController()),
builder -> builder.defaultRequest(get("/")
.contextPath("/app").servletPath("/main")
.accept(MediaType.APPLICATION_JSON)).build());
val mockMvc =
MockMvcTester.of(listOf(HotelController())) { builder: StandaloneMockMvcBuilder ->
builder.defaultRequest<StandaloneMockMvcBuilder>(
MockMvcRequestBuilders.get("/")
.contextPath("/app").servletPath("/main")
.accept(MediaType.APPLICATION_JSON)
).build()
}
前面的属性会影响通过 mockMvc
实例执行的每个请求。如果在给定的请求上也指定了相同的属性,它将覆盖默认值。这就是在默认请求中 HTTP 方法和 URI 无关紧要的原因,因为必须在每个请求中指定它们。
The preceding properties affect every request performed through the mockMvc
instance.
If the same property is also specified on a given request, it overrides the default
value. That is why the HTTP method and URI in the default request do not matter, since
they must be specified on every request.