springboot~3.x項(xiàng)目中使用集成測(cè)試
在 Spring Boot 3.x 中為控制器編寫集成測(cè)試,主要是通過(guò) @SpringBootTest 注解加載完整的應(yīng)用上下文,并利用 MockMvc 或 TestRestTemplate 來(lái)模擬 HTTP 請(qǐng)求并驗(yàn)證響應(yīng)。下面我將為你提供一個(gè)清晰的指南和代碼示例。
兩種測(cè)試
在Spring Boot項(xiàng)目中,測(cè)試通常分為單元測(cè)試和集成測(cè)試。以下是區(qū)分這兩種測(cè)試的一些指導(dǎo)原則:
單元測(cè)試
- 定義:?jiǎn)卧獪y(cè)試主要用于測(cè)試代碼中的單個(gè)“單元”,通常是一個(gè)方法或類。它們的目標(biāo)是驗(yàn)證特定功能的正確性。
- 特征:
- 獨(dú)立性:?jiǎn)卧獪y(cè)試不依賴于外部系統(tǒng)(如數(shù)據(jù)庫(kù)、網(wǎng)絡(luò)等)。
- 快速執(zhí)行:通常執(zhí)行時(shí)間很短。
- 使用Mock:通常會(huì)使用Mock對(duì)象來(lái)替代依賴項(xiàng),以便只測(cè)試目標(biāo)單元。
- 示例:
@SpringBootTest public class UserServiceTest { @Mock private UserRepository userRepository; @InjectMocks private UserService userService; @Test public void testGetUserById() { // Arrange User user = new User(1, "John Doe"); when(userRepository.findById(1)).thenReturn(Optional.of(user)); // Act User result = userService.getUserById(1); // Assert assertEquals("John Doe", result.getName()); } }
集成測(cè)試
- 定義:集成測(cè)試用于測(cè)試多個(gè)組件之間的交互,通常是測(cè)試整個(gè)應(yīng)用程序或其部分的行為。
- 特征:
- 依賴性:集成測(cè)試通常會(huì)啟動(dòng)Spring上下文,并可能連接到數(shù)據(jù)庫(kù)或其他外部服務(wù)。
- 較慢執(zhí)行:由于涉及多個(gè)組件,執(zhí)行時(shí)間通常較長(zhǎng)。
- 真實(shí)環(huán)境:測(cè)試在接近真實(shí)環(huán)境的條件下運(yùn)行。
- 示例:
@SpringBootTest @AutoConfigureMockMvc public class UserControllerTest { @Autowired private MockMvc mockMvc; @Test public void testGetUser() throws Exception { mockMvc.perform(get("/users/1")) .andExpect(status().isOk()) .andExpect(jsonPath("$.name").value("John Doe")); } }
如何區(qū)分
-
測(cè)試目標(biāo):
- 單元測(cè)試:關(guān)注單個(gè)類或方法。
- 集成測(cè)試:關(guān)注多個(gè)組件的協(xié)作。
-
使用的工具:
- 單元測(cè)試:Mockito、JUnit等。
- 集成測(cè)試:Spring Test、MockMvc等。
-
項(xiàng)目結(jié)構(gòu):
- 可以在
src/test/java目錄中創(chuàng)建不同的包,例如unit和integration,分別存放單元測(cè)試和集成測(cè)試。
- 可以在
-
命名約定:
- 可以在文件名中添加前綴或后綴,例如
UserServiceTest(單元測(cè)試)和UserControllerIntegrationTest(集成測(cè)試)。
- 可以在文件名中添加前綴或后綴,例如
?? Spring Boot 3.x 控制器集成測(cè)試指南
?? 1. 添加測(cè)試依賴
確保你的 pom.xml 中包含 Spring Boot Test starter 依賴,它通常已經(jīng)包含了 JUnit Jupiter、Mockito、AssertJ 等測(cè)試所需的庫(kù)。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
?? 2. 創(chuàng)建控制器
假設(shè)你有一個(gè)簡(jiǎn)單的 REST 控制器 ExampleController:
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class ExampleController {
@GetMapping("/greet")
public String greet(@RequestParam(required = false, defaultValue = "World") String name) {
return "Hello, " + name + "!";
}
@PostMapping("/users")
public User createUser(@RequestBody User user) {
// 假設(shè)這是一個(gè)創(chuàng)建用戶并返回創(chuàng)建后信息的服務(wù)
return userService.save(user); // userService 需要通過(guò)依賴注入
}
}
?? 3. 編寫集成測(cè)試類
創(chuàng)建一個(gè)集成測(cè)試類,使用 @SpringBootTest 和 @AutoConfigureMockMvc 來(lái)配置測(cè)試環(huán)境。
3.1 使用 MockMvc (模擬 MVC 環(huán)境)
這種方法不會(huì)啟動(dòng)真正的服務(wù)器,而是模擬 Servlet 環(huán)境,測(cè)試速度較快。
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import com.fasterxml.jackson.databind.ObjectMapper;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
// 使用 @SpringBootTest 加載完整的應(yīng)用程序上下文
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) // WebEnvironment.MOCK 是默認(rèn)值,可省略
@AutoConfigureMockMvc // 自動(dòng)配置 MockMvc
public class ExampleControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper; // Jackson 庫(kù)的 ObjectMapper,用于對(duì)象與 JSON 轉(zhuǎn)換
@Test
public void testGreetEndpoint() throws Exception {
mockMvc.perform(get("/api/greet") // 發(fā)起 GET 請(qǐng)求
.param("name", "Spring")) // 添加請(qǐng)求參數(shù)
.andExpect(status().isOk()) // 斷言狀態(tài)碼為 200
.andExpect(content().string("Hello, Spring!")); // 斷言響應(yīng)內(nèi)容
}
@Test
public void testGreetEndpointWithDefault() throws Exception {
mockMvc.perform(get("/api/greet"))
.andExpect(status().isOk())
.andExpect(content().string("Hello, World!"));
}
@Test
public void testCreateUser() throws Exception {
User newUser = new User("Alice", "alice@example.com");
// 將 User 對(duì)象轉(zhuǎn)換為 JSON 字符串
String userJson = objectMapper.writeValueAsString(newUser);
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON) // 設(shè)置請(qǐng)求內(nèi)容類型
.content(userJson)) // 設(shè)置請(qǐng)求體 JSON 內(nèi)容
.andExpect(status().isCreated()) // 斷言狀態(tài)碼為 201(假設(shè)創(chuàng)建成功返回 201)
.andExpect(jsonPath("$.name").value("Alice")) // 使用 JsonPath 斷言返回的 JSON 字段值
.andExpect(jsonPath("$.email").value("alice@example.com"));
}
}
3.2 使用 TestRestTemplate (啟動(dòng)真實(shí)服務(wù)器)
這種方法會(huì)啟動(dòng)一個(gè)嵌入式的真實(shí)服務(wù)器(如 Tomcat),測(cè)試更接近于生產(chǎn)環(huán)境。
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import static org.assertj.core.api.Assertions.assertThat;
// 使用 RANDOM_PORT 啟動(dòng)一個(gè)嵌入式服務(wù)器并監(jiān)聽(tīng)隨機(jī)端口,避免端口沖突
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ExampleControllerWithServerTest {
@Autowired
private TestRestTemplate restTemplate; // 注入 TestRestTemplate
@Test
public void testGreetEndpoint() {
// 使用 TestRestTemplate 發(fā)起請(qǐng)求
ResponseEntity<String> response = restTemplate.getForEntity("/api/greet?name=Spring", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isEqualTo("Hello, Spring!");
}
// 也可以測(cè)試 POST 請(qǐng)求
@Test
public void testCreateUser() {
User newUser = new User("Bob", "bob@example.com");
ResponseEntity<User> response = restTemplate.postForEntity("/api/users", newUser, User.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
assertThat(response.getBody()).isNotNull();
assertThat(response.getBody().getName()).isEqualTo("Bob");
assertThat(response.getBody().getEmail()).isEqualTo("bob@example.com");
}
}
?? 4. 關(guān)鍵注解和配置說(shuō)明
下表總結(jié)了集成測(cè)試中常用的注解及其用途:
| 注解 | 說(shuō)明 | 適用場(chǎng)景 |
|---|---|---|
@SpringBootTest |
加載完整的 Spring 應(yīng)用程序上下文,用于集成測(cè)試。 | 所有類型的集成測(cè)試 |
webEnvironment = WebEnvironment.MOCK |
默認(rèn)值。提供模擬的 Servlet 環(huán)境,不啟動(dòng)真實(shí)服務(wù)器。依賴 @AutoConfigureMockMvc。 |
使用 MockMvc 進(jìn)行控制層測(cè)試,速度較快 |
webEnvironment = WebEnvironment.RANDOM_PORT |
啟動(dòng)嵌入式服務(wù)器并監(jiān)聽(tīng)隨機(jī)端口。 | 使用 TestRestTemplate 或 WebTestClient 進(jìn)行測(cè)試 |
webEnvironment = WebEnvironment.DEFINED_PORT |
使用 application.properties 中定義的端口(或默認(rèn)的 8080)啟動(dòng)服務(wù)器。 |
需要固定端口的測(cè)試場(chǎng)景 |
@AutoConfigureMockMvc |
自動(dòng)配置 MockMvc 實(shí)例,用于模擬 MVC 請(qǐng)求。通常與 WebEnvironment.MOCK 結(jié)合使用。 |
使用 MockMvc 進(jìn)行測(cè)試時(shí)必需 |
@Test |
JUnit Jupiter 注解,標(biāo)記一個(gè)方法為測(cè)試方法。 | 所有測(cè)試方法 |
@Import |
顯式導(dǎo)入特定的配置類,用于測(cè)試。 | 需要覆蓋特定配置或引入測(cè)試專用配置時(shí) |
?? 5. 處理依賴和模擬(Mocking)
在集成測(cè)試中,你通常希望測(cè)試完整的集成鏈,因此應(yīng)盡量避免模擬(Mocking)。但如果某些外部依賴(如數(shù)據(jù)庫(kù)、第三方服務(wù))無(wú)法在測(cè)試環(huán)境中使用,或者你想隔離測(cè)試特定層,Spring Boot 提供了 @MockBean 注解來(lái)模擬 Bean。
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import com.example.service.UserService;
import com.fasterxml.jackson.databind.ObjectMapper;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@SpringBootTest
@AutoConfigureMockMvc
public class ExampleControllerWithMockServiceTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@MockBean // 模擬 UserService 這個(gè) Bean,真實(shí)的 UserService 不會(huì)被調(diào)用
private UserService userService;
@Test
public void testCreateUserWithMockService() throws Exception {
User inputUser = new User("Charlie", "charlie@example.com");
User savedUser = new User(1L, "Charlie", "charlie@example.com"); // 假設(shè)保存后有了 ID
// 模擬 userService.save() 方法的行為
when(userService.save(any(User.class))).thenReturn(savedUser);
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(inputUser)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.name").value("Charlie"))
.andExpect(jsonPath("$.email").value("charlie@example.com"));
}
}
?? 6. 運(yùn)行測(cè)試
你可以使用 IDE 中的運(yùn)行測(cè)試功能,或者通過(guò) Maven 命令運(yùn)行測(cè)試:
mvn test
Maven 會(huì)在 src/test/java 目錄下查找測(cè)試類并運(yùn)行。
?? 核心要點(diǎn)
- 集成測(cè)試 (Integration Test):使用
@SpringBootTest加載完整的應(yīng)用程序上下文,測(cè)試各個(gè)組件之間的集成情況。 - MockMvc:適用于模擬 MVC 環(huán)境的測(cè)試,無(wú)需啟動(dòng)真實(shí)服務(wù)器,速度快。 使用
perform發(fā)起請(qǐng)求,andExpect進(jìn)行斷言。 - TestRestTemplate:適用于啟動(dòng)真實(shí)嵌入式服務(wù)器的測(cè)試,更接近真實(shí)環(huán)境。 直接發(fā)起 HTTP 請(qǐng)求并接收響應(yīng)。
- 隨機(jī)端口:使用
WebEnvironment.RANDOM_PORT可以避免測(cè)試時(shí)的端口沖突問(wèn)題。 - @MockBean:當(dāng)你需要模擬應(yīng)用程序上下文中的某個(gè) Bean 時(shí)(例如模擬一個(gè)不易在測(cè)試環(huán)境中構(gòu)建的外部服務(wù)),可以使用此注解。
希望這個(gè)指南能幫助你在 Spring Boot 3.x 中順利編寫控制器的集成測(cè)試!
浙公網(wǎng)安備 33010602011771號(hào)