將 MockMvc 與 SpringBootTest 結合使用與使用 WebMvcTest
1. 概述
讓我們深入了解 Spring Boot 測試的世界!在本教程中,我們將深入研究@SpringBootTest
和@WebMvcTest
註解。我們將探討何時以及為何使用每一種,以及它們如何協同工作來測試我們的 Spring Boot 應用程式。另外,我們將揭示MockMvc
的內部工作原理以及它如何與整合測試中的兩個註釋互動。
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!");
}
接下來,我們還將有一個 post 方法,它將陣列作為 JSON 主體並傳回排序後的陣列作為回應。測試此類方法將有助於我們理解MockMvc:
@PostMapping
public ResponseEntity<List<Integer>> sort(@RequestBody List<Integer> arr){
return ResponseEntity.ok(sortingService.sortArray(arr));
}
4.比較@SpringBootTest
和@WebMvcTest
@WebMvcTest
註解位於org.springframework.boot.test.autoconfigure.web.servlet
套件中,而@SpringBootTest
位於org.springframework.boot.test.context.
預設情況下,假設我們計劃測試我們的應用程序,Spring Boot 會為我們的專案添加必要的依賴項。在班級級別,我們可以一次使用其中之一。
4.1.使用MockMvc
在@SpringBootTest
上下文中, MockMvc
將自動從控制器呼叫實際的服務實作。服務層 bean 將在應用程式上下文中可用。要在我們的測試中使用MockMvc
,我們需要加入@AutoConfigureMockMvc
註解。此註釋會建立MockMvc
的實例,將其註入到mockMvc
變數中,並使其準備好進行測試,而無需手動配置:
@AutoConfigureMockMvc
@SpringBootTest
class SortingControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
}
在@WebMvcTest
中, MockMvc
會配合服務層的@MockBean
來模擬服務層回應,而不需要呼叫真正的服務。此外,服務層 bean 不包含在應用程式上下文中。它預設提供@AutoConfigureMockMvc
:
@WebMvcTest
class SortingControllerUnitTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private SortingService sortingService;
}
注意:當將@SpringBootTest
與webEnvironment=RANDOM_PORT
一起使用時,請謹慎使用MockMvc
因為MockMvc
會嘗試確保處理 Web 請求所需的一切都已就位,並且在webEnvironment=RANDOM_PORT
時不會啟動Servlet 容器(處理傳入的HTTP 請求並產生回應) webEnvironment=RANDOM_PORT
嘗試啟動 servlet 容器。如果結合使用,它們就會互相矛盾。
4.2.什麼是自動配置?
在@WebMvcTest
中,Spring Boot 會自動設定MockMvc
實例、 DispatcherServlet
、 HandlerMapping
、 HandlerAdapter
和ViewResolvers
。它還掃描@Controller
、 @ControllerAdvice
、 @JsonComponent
、 Converter
、 GenericConverter
、 Filter
、 WebMvcConfigurer
和HandlerMethodArgumentResolver
組件。 一般來說,它會自動配置與Web層相關的元件。
@SpringBootTest
載入@SpringBootApplication
(SpringBootConfiguration+ EnableAutoConfiguration + ComponentScan) 所做的一切,即成熟的應用程式上下文。它甚至會載入application.properties
檔案和設定檔相關資訊。它還允許 bean 像使用@Autowired
一樣注入。
4.3.輕量級或重量級
我們可以說@SpringBootTest
是重量級的,因為預設情況下它主要配置為整合測試,除非我們想使用任何模擬。它還擁有應用程式上下文中的所有 bean。這也是它比其他人慢的原因。
另一方面, @WebMvcTest
更孤立,只關心 MVC 層。它非常適合單元測試。我們也可以特定於一個或多個控制器。它在應用程式上下文中的 Bean 數量有限。此外,在運行時,我們可以觀察到測試案例完成的相同時間差(即使用@WebMvcTes
t 的運行時間更少)。
4.4.測試期間的Web環境
當我們啟動一個真正的應用程式時,我們通常會點擊「http://localhost:8080」來存取我們的應用程式。為了在測試期間模擬相同的場景,我們使用`webEnvironment` 。我們使用它為測試案例定義一個連接埠(類似於 URL 中的 8080)。 @SpringBootTest
可以介入模擬的webEnvironment (WebEnvironment.MOCK)
或真實的webEnvironment (WebEnvironment.RANDOM_PORT)
,而@WebMvcTest
只提供模擬的測試環境。
以下是@SpringBootTest
和 W ebEnvironment
的程式碼範例:
@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.依賴關係
@WebMvcTest
不會自動偵測控制器所需的依賴項,因此我們必須模擬它們。而@SpringBootTest
會自動執行此操作。
在這裡我們可以看到我們使用了@MockBean
因為我們從控制器內部呼叫服務:
@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()
來our
服務類別中的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
、DB連接和@Service
,使其適合整合和系統測試,類似於生產環境。
在使用MockMvc
時, @SpringBootTest
內部從控制器調用實際的服務實現,而@WebMvcTest
則伴隨@MockBean
用於模擬服務層響應而不調用真正的服務。
與往常一樣,本教學的程式碼可在 GitHub 上取得。