執行請求
本節展示如何使用 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 進行測試,請務必相應地設定 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 無關緊要的原因,因為它們必須在每個請求中都指定。