执行请求
本节展示了如何使用 MockMvcTester 执行请求,以及它与 AssertJ 的集成以验证响应。
MockMvcTester 提供了一套流畅的 API 来构建请求,它重用了与 Hamcrest 支持相同的 MockHttpServletRequestBuilder,但无需导入静态方法。所返回的构建器具备 AssertJ 感知能力,因此将其包装在常规的 assertThat() 工厂方法中会触发请求交换,并提供一个专用于 MvcTestResult 的断言对象。
以下是一个简单的示例,它向 POST 发起一个 /hotels/42 请求,并配置请求以指定一个 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)的不同部分。与上面示例中使用单个语句不同,你可以使用 .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。
文件上传
您可以执行文件上传请求,这些请求在内部使用 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 和上下文路径
在大多数情况下,最好从请求 URI 中省略上下文路径和 Servlet 路径。如果你必须使用完整的请求 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 并不重要,因为它们必须在每个请求中明确指定。