执行请求

本节介绍如何使用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

多部分

您可以执行文件上传请求,这些请求在内部使用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无关紧要的原因,因为它们必须在每个请求上指定。