执行请求

本节展示如何使用MockMvcTester执行请求及其与AssertJ的集成以验证响应。spring-doc.cadn.net.cn

MockMvcTester 提供了一个流畅的API来组合请求,该请求重用了与Hamcrest支持相同的 MockHttpServletRequestBuilder,但无需导入静态方法。返回的构建器是AssertJ感知的,因此将其包装在常规的 assertThat() 工厂方法中会触发交换并提供对专用断言对象MvcTestResult的访问。spring-doc.cadn.net.cn

这是一个简单的示例,它对POST执行操作/hotels/42并配置请求以指定Accept头部:spring-doc.cadn.net.cn

assertThat(mockMvc.post().uri("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON))
		. // ...
assertThat(mockMvc.post().uri("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON))
	. // ...

AssertJ通常包含多个断言语句来验证交换的不同部分。与上述单个语句的情况不同,您可以使用extract()来返回一个Extractor,该提取器可用于多个assertThat()语句:spring-doc.cadn.net.cn

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 模板样式中指定查询参数,如下例所示:spring-doc.cadn.net.cn

assertThat(mockMvc.get().uri("/hotels?thing={thing}", "somewhere"))
		. // ...
assertThat(mockMvc.get().uri("/hotels?thing={thing}", "somewhere"))
	. // ...

您还可以添加表示查询或表单参数的Servlet请求参数,如下例所示:spring-doc.cadn.net.cn

assertThat(mockMvc.get().uri("/hotels").param("thing", "somewhere"))
		. // ...
assertThat(mockMvc.get().uri("/hotels").param("thing", "somewhere"))
	. // ...

如果应用程序代码依赖于Servlet请求参数并且没有显式检查查询字符串(通常情况下),那么你使用哪个选项并不重要。但是,请记住,通过URI模板提供的查询参数会被解码,而通过param(…​)方法提供的请求参数则需要已经解码。spring-doc.cadn.net.cn

异步

如果请求的处理是异步进行的,exchange()将等待请求完成,以便断言的结果实际上是不可变的。默认的超时时间是10秒,但可以根据每个请求的情况来控制,如下例所示:spring-doc.cadn.net.cn

assertThat(mockMvc.get().uri("/compute").exchange(Duration.ofSeconds(5)))
		. // ...
assertThat(mockMvc.get().uri("/compute").exchange(Duration.ofSeconds(5)))
	. // ...

如果您希望获取原始结果并自己管理异步请求的生命周期,请使用asyncExchange而不是exchangespring-doc.cadn.net.cn

多部分

您可以执行文件上传请求,这些请求内部使用 MockMultipartHttpServletRequest 以便不实际解析多部分 请求。相反,您需要将其设置得类似于以下示例:spring-doc.cadn.net.cn

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路径

在大多数情况下,最好将上下文路径和Servlet路径排除在请求URI之外。如果你必须使用完整的请求URI进行测试,请确保相应地设置contextPathservletPath,以便请求映射能够正常工作,如下例所示:spring-doc.cadn.net.cn

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 会很繁琐。相反,您可以设置默认的请求属性,如下例所示:spring-doc.cadn.net.cn

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无关紧要,因为它们必须在每个请求中指定。spring-doc.cadn.net.cn