執行請求

本節展示如何使用 `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`。

Multipart

您可以執行檔案上傳請求,這些請求內部使用 `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 和上下文路徑

在大多數情況下,最好將上下文路徑和 Servlet 路徑排除在請求 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 無關緊要,因為它們必須在每個請求上指定。