执行请求
本节展示了如何使用MockMvcTester来发送请求,以及如何将其与AssertJ集成以验证响应。
MockMvcTester 提供了一个流畅的 API 来构建请求,该 API 重用了与 Hamcrest 支持相同的 MockHttpServletRequestBuilder,只不过无需导入静态方法。返回的构建器支持 AssertJ,因此将其包装在常规的 assertThat() 工厂方法中即可触发请求交换过程,并为 MvcTestResult 提供专门的断言对象。
下面是一个简单的示例,它对 /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)的不同部分。与上面的情况不同,你可以使用.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(…)方法提供的请求参数则预期已经是解码状态了。
async
如果请求的处理是异步完成的,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 request)了。相反,你需要将其设置得类似于以下示例:
- 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 和上下文路径
在大多数情况下,最好将上下文路径(context path)和Servlet路径(Servlet path)从请求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并不重要,因为每个请求都必须指定这些信息。