执行请求

本节展示如何使用 MockMvcTester 执行请求及其与 AssertJ 的集成以验证响应。

MockMvcTester 提供了一个流畅的 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() 返回一个 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,因此实际上没有对多部分请求进行解析。相反,您必须像以下示例那样进行设置

  • 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 进行测试,请务必相应地设置 contextPathservletPath,以便请求映射正常工作,示例如下

  • 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"))
	. // ...

在前面的示例中,为每个执行的请求设置 contextPathservletPath 会很麻烦。相反,您可以设置默认请求属性,示例如下

  • 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 无关紧要的原因,因为它们必须在每个请求中都指定。

© . This site is unofficial and not affiliated with VMware.