1. 概述
讓我們深入探索 Spring Boot 測試的世界!在本教程中,我們將深入研究 <em @SpringBootTest</em>> 和 <em @WebMvcTest</em>> 註解。我們將探討何時以及為什麼使用每種註解,以及它們如何協同工作,以測試我們的 Spring Boot 應用程序。此外,我們還將揭示 <em MockMvc</em>> 的內部工作原理,以及它如何與這兩種註解在集成測試中交互。
2. 什麼是 @WebMvcTest 和 @SpringBootTest?
@WebMvcTest 註解用於創建與 MVC(或更具體地説,控制器)相關的測試。 還可以配置它來測試特定的控制器。 它主要簡化了對 Web 層的加載和測試。
@SpringBootTest 註解用於創建一個測試環境,通過加載完整的應用程序上下文(如帶有 @Component 和 @Service 註解的類、數據庫連接等)。 它查找主類(具有 @SpringBootApplication 註解的類)並使用它來啓動應用程序上下文。
這兩個註解是在 Spring Boot 1.4 版本中引入的。
3. 項目設置
對於本教程,我們將創建兩個類,即 SortingController 和 SortingService。 SortingController 接收一個包含整數列表的請求,並使用一個輔助類 SortingService,該類包含用於對列表進行排序的業務邏輯。
我們將使用構造函數注入來獲取 SortingService 依賴,如下所示:
@RestController
public class SortingController {
private final SortingService sortingService;
public SortingController(SortingService sortingService){
this.sortingService=sortingService;
}
// ...
}讓我們聲明一個 GET 方法來檢查我們的服務器是否運行,這也有助於我們探索測試過程中註解的工作方式:
@GetMapping
public ResponseEntity<String> helloWorld(){
return ResponseEntity.ok("Hello, World!");
}接下來,我們還將有一個使用 JSON 格式的數組作為請求體,並返回排序後的數組作為響應的 POST 方法。測試這種類型的請求方法將幫助我們理解 MockMvc 的使用。
@PostMapping
public ResponseEntity<List<Integer>> sort(@RequestBody List<Integer> arr){
return ResponseEntity.ok(sortingService.sortArray(arr));
}4. 比較<em @SpringBootTest和<em @WebMvcTest
<em @SpringBootTest</em> 註解位於 <em org.springframework.boot.test.autoconfigure.web.servlet</em> 包中,而<em @WebMvcTest</em> 註解位於 <em org.springframework.boot.test.context</em> 包中。 Spring Boot 默認情況下,會將必要的依賴添加到我們的項目中,假設我們計劃測試我們的應用程序。 在類級別上,我們可以同時使用這兩種註解。
4.1. 使用 MockMvc
在 @SpringBootTest 上下文中,MockMvc 將會自動調用控制器中實際的服務實現。服務層 Bean 將在應用程序上下文中可用。為了在我們的測試中使用 MockMvc,我們需要添加 @AutoConfigureMockMvc 註解。此註解會創建一個 MockMvc 實例,將其注入到 mockMvc 變量中,並使其準備好進行測試,而無需手動配置:
@AutoConfigureMockMvc
@SpringBootTest
class SortingControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
}在 <em @WebMvcTest</em>>, <em @MockMvc</em> 將會與服務層 <em @MockBean</em>> 協同使用,以模擬服務層響應,而無需調用真實的 Service。 此外,服務層 Bean 不包含在應用程序上下文中。 它默認提供 <em @AutoConfigureMockMvc</em>>:
@WebMvcTest
class SortingControllerUnitTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private SortingService sortingService;
}注意:在使用 @SpringBootTest 且 webEnvironment=RANDOM_PORT 時,應謹慎使用 MockMvc,因為 MockMvc 試圖確保用於處理 Web 請求所需的一切都已就位,同時不會啓動任何 Servlet 容器(用於處理傳入的 HTTP 請求並生成響應),而 webEnvironment=RANDOM_PORT 則試圖啓動 Servlet 容器。兩者若同時使用,會相互矛盾。
4.2. 什麼是自動配置?
在 <em @WebMvcTest</em> 中,Spring Boot 會自動配置 <em MockMvc</em> 實例、<em DispatcherServlet</em>、<em HandlerMapping</em>、<em HandlerAdapter</em> 和 <em ViewResolvers</em>。 它還會掃描 <em @Controller</em>、<em @ControllerAdvice</em>、<em @JsonComponent</em>、<em Converter</em>、<em GenericConverter</em>、<em Filter</em>、<em WebMvcConfigurer</em> 和 <em HandlerMethodArgumentResolver</em> 組件。 總體而言,它會自動配置與 Web 層相關的組件。
<em @SpringBootTest</em> 會加載 <em @SpringBootApplication</em> (Spring BootConfiguration + EnableAutoConfiguration + ComponentScan) 所做的事情,即一個完整的應用程序上下文。 它甚至會加載 <em application.properties</em> 文件以及與 profile 相關的配置信息。 它還允許 bean 使用 <em @Autowired</em> 進行注入。
4.3. 輕量級或重量級
可以説,@SpringBootTest 默認配置為集成測試,因此屬於重量級,除非我們想要使用任何 Mock 對象。
它還包含應用程序上下文中的所有 Bean。這也是它相對於其他測試方式更慢的原因。
另一方面,@WebMvcTest 則更加隔離,只關注 MVC 層級,非常適合單元測試。 我們可以針對一個或多個控制器進行具體配置。 它在應用程序上下文中擁有較少的 Bean。 此外,在運行過程中,我們仍然可以觀察到相同的性能差異(即使用 @WebMvcTest 測試用例完成時間更短)。
4.4. 測試環境中的 Web 環境
當我們啓動實際應用時,通常通過“http://localhost:8080”訪問我們的應用。為了在測試過程中模擬相同的場景,我們使用 <em>webEnvironment</em>。通過它,我們可以為測試用例定義一個端口(類似於 URL 中的 8080)。<em>@SpringBootTest</em> 可以同時在模擬的 <em>webEnvironment (WebEnvironment.MOCK)</em> 或真實的 <em>webEnvironment (WebEnvironment.RANDOM_PORT)</em> 中運行,而 <em>@WebMvcTest</em> 僅提供模擬的測試環境。
以下是示例代碼:使用 <em>@SpringBootTest</em> 和 <em>WebEnvironment</em>:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class SortingControllerWithWebEnvironmentIntegrationTest {
@LocalServerPort
private int port;
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private ObjectMapper objectMapper;
}現在讓我們用它們在行動中編寫測試用例。以下是 GET 方法的測試用例:
@Test
void whenHelloWorldMethodIsCalled_thenReturnSuccessString() {
ResponseEntity<String> response = restTemplate.getForEntity("http://localhost:" + port + "/", String.class);
Assertions.assertEquals(HttpStatus.OK, response.getStatusCode());
Assertions.assertEquals("Hello, World!", response.getBody());
}以下是用於檢查 POST 方法正確性的測試用例:
@Test
void whenSortMethodIsCalled_thenReturnSortedArray() throws Exception {
List<Integer> input = Arrays.asList(5, 3, 8, 1, 9, 2);
List<Integer> sorted = Arrays.asList(1, 2, 3, 5, 8, 9);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
ResponseEntity<List> response = restTemplate.postForEntity("http://localhost:" + port + "/",
new HttpEntity<>(objectMapper.writeValueAsString(input), headers),
List.class);
Assertions.assertEquals(HttpStatus.OK, response.getStatusCode());
Assertions.assertEquals(sorted, response.getBody());
}4.5. 依賴關係
<em@WebMvcTest</em>> 不會自動檢測控制器所需的依賴項,因此我們需要手動 Mock 它們。而 <em@SpringBootTest</em>> 則會自動完成這項任務。
在這裏,我們使用了 <em@MockBean</em>>,因為我們正在從控制器內部調用一個服務:
@WebMvcTest
class SortingControllerUnitTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private SortingService sortingService;
}現在讓我們來看一個使用 MockMvc 與模擬 Bean 的測試示例:
@Test
void whenSortMethodIsCalled_thenReturnSortedArray() throws Exception {
List<Integer> input = Arrays.asList(5, 3, 8, 1, 9, 2);
List<Integer> sorted = Arrays.asList(1, 2, 3, 5, 8, 9);
when(sortingService.sortArray(input)).thenReturn(sorted);
mockMvc.perform(post("/").contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(input)))
.andExpect(status().isOk())
.andExpect(content().json(objectMapper.writeValueAsString(sorted)));
}
我們使用了 when().thenReturn() 來模擬 sortArray() 函數在 我們的 服務類中的調用。如果不這樣做,將會導致 NullPointerException
4.6. 自定義
@SpringBootTest 主要不適用於自定義,但 @WebMvcTest 可以自定義以僅與有限數量的控制器類一起使用。 在下面的示例中,我特別提到了 SortingController 類。因此,僅註冊了一個控制器及其依賴項與應用程序:
@WebMvcTest(SortingController.class)
class SortingControllerUnitTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private SortingService sortingService;
@Autowired
private ObjectMapper objectMapper;
}5. 結論
<@SpringBootTest> 和 <@WebMvcTest> 具有各自不同的用途。<@WebMvcTest> 旨在用於與 MVC 相關的測試,專注於 Web 層,併為特定控制器提供便捷的測試。另一方面,<@SpringBootTest> 通過加載完整的應用程序上下文,包括 <@Components>、數據庫連接和 <@Service>,從而創建一個測試環境,使其適用於集成和系統測試,類似於生產環境。
當使用 <@MockMvc> 時,<@SpringBootTest> 內部會調用控制器的實際服務實現,而 <@WebMvcTest> 則會伴隨使用 <@MockBean> 來模擬服務層響應,而無需調用真實的 Service。