執行請求

本節說明如何使用 MockMvcTester 執行請求,以及其與 AssertJ 的整合以驗證回應。

MockMvcTester 提供流暢的 API 來組合請求,該請求重複使用與 Hamcrest 支援相同的 MockHttpServletRequestBuilder,但不需要匯入靜態方法。傳回的 Builder 具有 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() 傳回可在多個 assertThat 語句中使用的 MvcTestResult

  • 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 和 Context 路徑

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