执行请求
本节展示如何使用 `MockMvcTester` 执行请求以及如何结合 AssertJ 验证响应。
`MockMvcTester` 提供流畅的 API 来组合请求,它复用与 Hamcrest 支持相同的 `MockHttpServletRequestBuilder`,但不需要导入静态方法。返回的构建器感知 AssertJ,因此将其包装在常规的 `assertThat()` 工厂方法中会触发交换并提供对 `MvcTestResult` 的专用 Assert 对象的访问。
这里有一个简单的示例,它对 `/hotels/42` 执行 `POST` 请求,并配置请求以指定 `Accept` 头部
-
Java
-
Kotlin
assertThat(mockMvc.post().uri("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON))
. // ...
assertThat(mockMvc.post().uri("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON))
. // ...
AssertJ 通常包含多个 `assertThat()` 语句来验证交换的不同部分。与上面的单个语句不同,您可以使用 `.exchange()` 返回一个 `MvcTestResult`,该对象可以在多个 `assertThat` 语句中使用
-
Java
-
Kotlin
MvcTestResult result = mockMvc.post().uri("/hotels/{id}", 42)
.accept(MediaType.APPLICATION_JSON).exchange();
assertThat(result). // ...
val result = mockMvc.post().uri("/hotels/{id}", 42)
.accept(MediaType.APPLICATION_JSON).exchange()
assertThat(result)
. // ...
您可以像下面的示例所示,使用 URI 模板风格指定查询参数
-
Java
-
Kotlin
assertThat(mockMvc.get().uri("/hotels?thing={thing}", "somewhere"))
. // ...
assertThat(mockMvc.get().uri("/hotels?thing={thing}", "somewhere"))
. // ...
您还可以添加表示查询参数或表单参数的 Servlet 请求参数,如下面的示例所示
-
Java
-
Kotlin
assertThat(mockMvc.get().uri("/hotels").param("thing", "somewhere"))
. // ...
assertThat(mockMvc.get().uri("/hotels").param("thing", "somewhere"))
. // ...
如果应用程序代码依赖于 Servlet 请求参数且不显式检查查询字符串(这通常是情况),则使用哪种选项都无关紧要。但请记住,通过 URI 模板提供的查询参数会被解码,而通过 `param(…)` 方法提供的请求参数则预期已解码。
异步
如果请求处理是异步的,`exchange()` 会等待请求完成,以便断言结果是有效的不可变对象。默认超时时间为 10 秒,但可以按请求逐个控制,如下面的示例所示
-
Java
-
Kotlin
assertThat(mockMvc.get().uri("/compute").exchange(Duration.ofSeconds(5)))
. // ...
assertThat(mockMvc.get().uri("/compute").exchange(Duration.ofSeconds(5)))
. // ...
如果您希望获取原始结果并自行管理异步请求的生命周期,请使用 `asyncExchange` 而不是 `exchange`。
Multipart
您可以执行文件上传请求,这些请求内部使用 `MockMultipartHttpServletRequest`,因此没有实际的 Multipart 请求解析。相反,您需要进行如下设置,使其类似于以下示例
-
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)))
. // ...
使用 Servlet 和上下文路径
在大多数情况下,最好将上下文路径和 Servlet 路径排除在请求 URI 之外。如果您必须使用完整的请求 URI 进行测试,请务必相应地设置 `contextPath` 和 `servletPath`,以便请求映射能够正常工作,如下面的示例所示
-
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` 会很麻烦。相反,您可以设置默认的请求属性,如下面的示例所示
-
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 无关紧要,因为它们必须在每个请求上指定。