八種測試方法,讓 Spring Boot 接口穩定性提升 200%
環境:SpringBoot3.4.2
1. 簡介
在前后端分離架構中,Controller 作為系統的 "門面",承擔著接收請求、處理參數、調用業務邏輯并返回響應的核心職責。其接口的穩定性直接關系到整個系統的可用性與用戶體驗。
因此,進行系統化的 Controller 接口測試至關重要。它不僅能驗證 API 的正確性、保障數據交互的準確性,還能提前暴露集成缺陷,確保在各種邊界和異常場景下系統行為符合預期,是提升軟件質量、支撐持續交付的關鍵環節。
通常Controller層主要做如下的驗證:
- 請求映射
驗證 HTTP 方法(GET/POST 等)、路徑(/users/{id})是否正確映射到目標方法。 - 請求/響應的序列化與反序列化
確保 JSON 與 Java 對象之間的轉換(@RequestBody, @ResponseBody)準確無誤。 - 驗證、異常處理與 HTTP 狀態碼
測試數據校驗(@Valid)、全局異常處理器(@ControllerAdvice)能否正確返回 400、404、500 等狀態碼。 - 與服務層的集成
驗證 Controller 是否正確調用 Service 方法,參數傳遞無誤。可通過 @MockBean 模擬依賴,或連接真實服務進行測試。
2.實戰案例
準備環境
public record User(Long id, String name, Integer age) {
}
@Service
public class UserService {
public User queryUser(Long id) {
return new User(id, "Pack_xg", 33) ;
}
public List<User> queryUsers() {
return List.of(new User(1L, "pack", 33)) ;
}
public User save(User user) {
System.err.println("創建用戶...") ;
return user ;
}
}
// Controller接口
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService ;
public UserController(UserService userService) {
this.userService = userService ;
}
@GetMapping("/{id}")
public User queryUser(@PathVariable Long id) {
return this.userService.queryUser(id);
}
@GetMapping("")
public List<User> queryUsers() {
return this.userService.queryUsers() ;
}
@PostMapping("")
@ResponseStatus(code = HttpStatus.CREATED)
public User save(@RequestBody User user) {
return user ;
}
}接下來,我們將通過8種方法對上面的接口進行不同場景下的測試。
2.1 使用 @WebMvcTest
目的
該方法通過模擬所有依賴項(如服務層、數據訪問層),實現對控制器層的獨立隔離測試。
你可以驗證以下關鍵點:
- ? URL 映射:例如,GET /users 是否正確調用了對應的處理方法。
- ? 請求/響應處理:請求參數和請求體能否正確反序列化,響應對象能否正確序列化為 JSON 或 XML。
- ? HTTP 狀態碼與錯誤響應:正常請求返回 200 OK,資源未找到返回 404 Not Found,參數校驗失敗返回 400 Bad Request 等。
- ? 與模擬服務的交互:控制器是否按預期調用了服務層方法,傳遞的參數是否正確,行為是否符合設計。
核心注解&類說明:
- @WebMvcTest(YourController.class) :Spring Boot 僅加載 Web 層(如控制器、攔截器、消息轉換器等),不會啟動完整的應用上下文,從而實現快速、專注的測試。
- MockMvc :用于模擬 HTTP 請求(如 GET、POST),并驗證響應(如狀態碼、響應體內容),無需啟動真實服務器。
- @MockBean: 將應用上下文中真實的依賴服務(例如 UserService)替換為 Mockito 模擬對象,從而隔離外部依賴,精準控制和驗證服務調用行為。
測試用例:
@WebMvcTest(UserController.class)
public class UserControllerTest1 {
// 用于模擬 HTTP 請求。
@Resource
private MockMvc mockMvc;
// 被模擬的服務(真實的服務被忽略)。
@MockitoBean
private UserService userService;
@Test
public void testUsers() throws Exception {
// 1.設置模擬行為
when(userService.queryUsers()).thenReturn(List.of(new User(1L, "pack", 33))) ;
// 2.模擬 HTTP GET 請求到 /users 并驗證響應
mockMvc.perform(get("/users"))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].name").value("pack"));
}
@Test
public void testUser() throws Exception {
when(userService.queryUser(1L))
.thenThrow(new UserNotFoundException());
mockMvc.perform(get("/users/1"))
.andExpect(status().isNotFound());
}
}? 優點
- ? 執行速度快:無需連接數據庫或進行真實網絡調用,測試運行迅速。
- ? 隔離控制器邏輯:排除服務層、數據層等外部干擾,精準驗證控制器自身行為。
- ? 適合驗證請求/響應格式:可有效測試 URL 映射、參數綁定、JSON 序列化、狀態碼及錯誤響應結構。
? 缺點
- ? 不測試真實的服務/數據層集成:由于依賴被模擬,無法發現真實調用中的集成問題。
- ? 對 Spring 上下文的驗證有限:某些全局配置(如安全過濾器鏈、自定義攔截器)的行為可能與完整應用環境存在差異。
何時使用 @WebMvcTest?
- 當你需要快速、專注地測試控制器邏輯時。
- 當你的重點是驗證 HTTP 響應格式、狀態碼和異常處理機制時。
- 當你不需要啟動完整的 Spring 上下文(如數據庫、消息隊列、完整安全配置)時。
2.2 @SpringBootTest + MockMvc 進行全面集成測試
目的
這種方法可測試整個應用堆棧,包括:
- ? 控制器(Controllers):HTTP 請求的接收、路由、參數綁定與響應處理。
- ? 服務層(Services):核心業務邏輯的正確執行。
- ? 數據訪問層(Repositories):與數據庫的實際交互,包括 CRUD 操作和事務管理。
- ? 配置(Configuration):安全策略(如 Spring Security)、過濾器、攔截器、消息轉換器、序列化設置等全局配置是否按預期生效。
與 @WebMvcTest 不同,此方法不會模擬服務或數據訪問層(除非顯式指定)。相反,它使用:
- 一個真實的(或內存中的)數據庫(例如,測試時使用 H2)
- 實際的服務層(除非需要,否則不進行模擬)
- 完整的 Spring 上下文(與生產環境類似)
核心注解 & 類
- @SpringBootTest:啟動完整的 Spring 應用程序上下文。
- @AutoConfigureMockMvc: 啟用 MockMvc 以進行 HTTP 測試(無需啟動真實服務器)。
測試用例:
@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerTest2 {
// 用于模擬 HTTP 請求。
@Resource
private MockMvc mockMvc;
// 被模擬的服務(真實的服務被忽略)。
@MockitoBean
private UserService userService;
@Test
public void testSave() throws Exception {
String jsonBody = """
{"id": 2, "name": "admin", "age": 33}
""";
mockMvc.perform(post("/users")
.contentType(MediaType.APPLICATION_JSON)
.content(jsonBody))
// 檢查狀態碼是否201
.andExpect(status().isCreated())
// 檢查返回值的name屬性是否是admin
.andExpect(jsonPath("$.name").value("admin"));
}
}? 優點
- ? 測試完整流程(控制器 → 服務 → 數據訪問層 → 數據庫)
- ? 發現集成問題(如 JSON 解析錯誤、數據庫約束沖突)
- ? 更貼近真實運行行為(相比模擬測試)
? 缺點
- ? 速度較慢(需啟動完整 Spring 上下文和數據庫)
- ? 需要配置測試數據庫(如 H2、Testcontainers 等)
- ? 難以定位問題(失敗可能來自任意一層)
何時使用 @SpringBootTest?
- 當你需要進行端到端測試(API 到數據庫)時。
- 當模擬不足以滿足需求時(例如測試事務、安全配置)。
- 當需要驗證真實數據庫約束(如唯一字段、外鍵關系)時。
2.3 使用 WebTestClient 進行測試
WebTestClient 是 MockMvc 和 TestRestTemplate 的現代、靈活替代方案,支持:
- ? 響應式應用(Spring WebFlux)
- ? 傳統的阻塞式控制器(Spring MVC)
- ? 流暢的鏈式 API,便于進行請求/響應驗證
它可以測試:
- HTTP 端點(REST、GraphQL 等)
- 響應狀態碼、響應頭和響應體
- 錯誤處理和流式響應
測試用例:
// 使用下面2個注解都可以
// @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@WebFluxTest(UserController.class)
public class UserControllerTest3 {
@Resource
private WebTestClient webTestClient;
@MockitoBean
private UserService userService;
@Test
public void testQueryUsers() throws Exception {
when(userService.queryUsers()).thenReturn(List.of(new User(1L, "pack", 33))) ;
webTestClient.get().uri("/users")
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$[0].name").isEqualTo("pack");
}
}? 優點
- ? 統一支持 MVC 與 WebFlux 應用
- ? 比 MockMvc 更易讀(流暢式 API 風格)
- ? 支持流式響應測試(如 SSE、WebSocket)
- ? 既可用于模擬環境,也可連接真實服務器
? 缺點
- ? 相對較新,部分團隊仍偏好 MockMvc
- ? 對于純 MVC 應用,相比 MockMvc 略缺乏細粒度控制
何時使用 WebTestClient?
- 若你的應用基于響應式(WebFlux)→ 首選方案。
- 若你希望使用統一工具測試 MVC 和 WebFlux 控制器。
- 若你偏好流暢、現代的斷言風格,而非 MockMvc 的 DSL 風格。
2.4 使用 TestRestTemplate 進行測試
目的
TestRestTemplate 是一個真實的 HTTP 客戶端,向正在運行的 Spring Boot 應用程序發送實際的 HTTP 請求。與模擬請求的 MockMvc 不同,它:
- ? 發起真實的網絡調用(如同瀏覽器或 Postman)
- ? 測試完整的服務器行為(包括過濾器、安全機制和錯誤處理)
- ? 可用于測試任何 REST 端點(不僅限于 Spring 控制器)
最適合用于:
- 端到端的 API 測試
- 認證功能測試(如 OAuth、JWT)
- 驗證負載均衡器與代理行為
測試用例:
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class UserControllerTest4 {
@Resource
private TestRestTemplate restTemplate;
@Test
public void testQueryUsers() throws Exception {
ResponseEntity<User[]> response = restTemplate.getForEntity("/users", User[].class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals("pack", response.getBody()[0].name());
}
}? 優點
- ? 測試真實的 HTTP 行為(請求頭、Cookie、SSL 等)
- ? 可用于測試外部 API(不僅限于 Spring 控制器)
- ? 適合與其他服務進行集成測試
? 缺點
- ? 速度較慢(需啟動完整服務器并進行網絡調用)
- ? 相比 MockMvc 控制力較弱(無法直接模擬內部行為)
- ? 更難調試(失敗可能源于網絡問題而非應用邏輯)
何時使用 TestRestTemplate?
- 測試 API 網關或代理
- 驗證 HTTPS、CORS 或安全過濾器的行為
- 需要與外部服務交互的端到端測試
2.5 獨立模式 MockMvc(無 Spring 上下文)
這種方式允許你在完全隔離的環境下測試單個控制器,無需:
- 加載 Spring 上下文
- 執行自動配置
- 觸發過濾器、攔截器或 AOP 通知
取而代之的是:
- ? 手動創建控制器(并注入模擬的依賴)
- ? 使用 MockMvcBuilders.standaloneSetup()(無需 @SpringBootTest)
- ? 獲得極快的測試執行速度(非常適合 TDD 快速反饋)
測試用例:
public class UserControllerTest5 {
private MockMvc mockMvc;
private UserService userService = mock(UserService.class);
@BeforeEach
public void setup() {
// 1.手動創建Controller接口
UserController controller = new UserController(userService);
// 2.不使用Spring構建MockMvc
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
@Test
public void testQueryUsers() throws Exception {
// 3.模擬測試行為
when(userService.queryUsers()).thenReturn(List.of(new User(1L, "pack", 33)));
// 4.測試接口
mockMvc.perform(get("/users"))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].name").value("pack1"));
}
}? 優點
- ? 極快執行(無 Spring 啟動開銷)
- ? 完全掌控依賴(無隱式 @Autowired 行為)
- ? 非常適合單元測試純控制器邏輯
? 缺點
- ? 不支持 Spring 功能(如 @Valid 數據驗證、@ControllerAdvice 異常處理、安全注解等)
- ? 需手動配置(必須顯式注入所有依賴)
- ? 不夠真實(行為可能與生產環境存在差異)
何時使用 Standalone MockMvc?
- 需要完全隔離地測試控制器內部邏輯時
- 進行超高速單元測試,追求極致的 TDD 快速反饋
- 希望徹底避免 Spring 上下文啟動開銷的場景
2.6 REST Assured — 流暢 API 測試
目的
REST Assured 是一個用于測試 REST API 的 Java 領域特定語言(DSL),采用流暢的、行為驅動(BDD)風格編寫測試。它:
- ? 使測試代碼更具可讀性(類似自然語言)
- ? 支持復雜驗證(JSON Path、XML、Schema 等)
- ? 可與任何 HTTP 服務器(Spring Boot、Node.js 等)配合使用
最適合用于:
- API 契約測試:驗證接口的請求/響應格式是否符合約定。
- 與外部服務的集成測試:測試應用與第三方 API 的交互。
- 采用 BDD(Given-When-Then)實踐的團隊:統一測試語言,提升協作效率。
測試用例:
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class UserControllerTest6 {
// 注入隨機端口
@LocalServerPort
private int port;
@Test
public void testQueryUsers() {
given().port(port) // 設置端口
// 當:調用 GET /users
.when().get("/users")
// 那么:驗證響應
.then().statusCode(200) // HTTP 200 OK
.body("[0].name", equalTo("pack")) // 檢查 JSON
.body("size()", greaterThan(1)); // 其他斷言
}
}注意,你需要引入如下依賴:
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>? 優點
- ? 語法可讀性強,采用 BDD 風格(清晰的 given-when-then 結構)
- ? 斷言功能強大(支持 JSON Path、Hamcrest 匹配器)
- ? 支持多種認證方式(OAuth2、Basic Auth 等
- ? 可用于測試任何 REST API(不僅限于 Spring Boot 應用)
? 缺點
- ? 需要引入額外依賴(增加 pom.xml 或 build.gradle 的體積)
- ? 相比 MockMvc 稍慢(通常基于真實 HTTP 調用)
- ? 學習曲線較陡(DSL 語法有其獨特性)
何時使用 REST Assured?
- 測試第三方或外部 API
- 編寫易于理解、可讀性高的集成測試
- 需要驗證復雜的 JSON/XML 響應結構時
2.7 測試 Controller Advice 與異常處理
目的
驗證以下關鍵環節:
- ? 全局異常處理器(@ControllerAdvice)能否正確捕獲并處理異常
- ? 自定義錯誤響應(JSON/XML 格式)是否符合 API 規范
- ? 返回的 HTTP 狀態碼是否與錯誤類型匹配(如 404、400、500 等)
首先,準備@ControllerAdvice全局異常處理
@RestControllerAdvice
public class UserControllerAdvice {
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<?> error(UserNotFoundException e) {
return ResponseEntity.status(404).body(Map.of("code", -1, "error", e.getMessage())) ;
}
}
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException() {
super();
}
public UserNotFoundException(String message) {
super(message);
}
public UserNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}測試用例:
@WebMvcTest(UserController.class)
public class UserControllerTest7 {
// 用于模擬 HTTP 請求。
@Resource
private MockMvc mockMvc;
// 被模擬的服務
@MockitoBean
private UserService userService;
@Test
public void testUser() throws Exception {
when(userService.queryUser(1L)).thenThrow(new UserNotFoundException("用戶不存在1"));
mockMvc.perform(get("/users/1"))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.error").value("用戶不存在"));
}
}最佳實踐
- ? 結合測試模擬與真實異常場景:既測試手動拋異常的路徑,也測試由框架觸發的異常(如參數校驗失敗)
- ? 驗證錯誤響應結構:確保返回的錯誤 JSON/XML 字段(如 code, message, timestamp)符合 API 文檔規范
- ? 在測試中包含錯誤日志輸出:使用 .andDo(print()) 打印請求/響應詳情,便于調試失敗用例
2.8 使用 Mockito 進行純單元測試
目的
該方法完全繞過 HTTP 協議和 Spring 框架,僅專注于測試 Java 方法調用。適用于:
- ? 隔離控制器邏輯(例如,調用服務前的請求參數處理與業務判斷)
- ? 極致快速的單元測試(無 Spring 上下文啟動、無 HTTP 開銷)
- ? 驗證與依賴組件的交互(如是否正確調用 UserService 及參數傳遞)
測試用例:
public class UserControllerTest8 {
private UserService userService = mock(UserService.class);
private UserController userController = new UserController(userService);
@Test
public void testQueryUsers() throws Exception {
when(userService.queryUsers()).thenReturn(List.of(new User(1L, "pack", 33)));
List<User> users = userController.queryUsers() ;
assertEquals("pack", users.get(0).name());
verify(userService).queryUsers() ;
}
}? 優點
- ? 速度最快(無任何框架開銷)
- ? 對依賴完全可控(所有依賴均可模擬)
- ? 適合測試控制器內的復雜業務邏輯
? 缺點
- ? 不測試 HTTP 映射(如 @GetMapping、@PostMapping 是否正確)
- ? 忽略序列化/驗證邏輯(無 JSON 轉換、無參數綁定過程)
- ? 無法覆蓋 Spring 特性(如 @Valid 校驗、安全注解等)
何時使用純 Mockito 測試?
- 測試控制器中的輔助方法或私有邏輯
- 驗證控制器內復雜的條件判斷或數據處理流程





















