目錄
- 一、什么是單元測試?
- 二、Spring Boot 中的單元測試依賴
- 三、舉例 Spring Boot 中不同層次的單元測試
- 3.1 Service層
- 3.2 Controller 層
- 3.3 Repository層
- 四、Spring Boot 中 Mock、Spy 對象的使用
- 4.1 使用Mock對象的背景
- 4.2 什么是Mock對象,有哪些好處?
- 4.3 使用 Mock 對象的示例
- 4.4 什么是Spy對象,有哪些好處?
- 4.5 使用 Spy 對象的示例

一、什么是單元測試?
單元測試
是指對軟件中的最小可測試單元進行檢查和驗證。在 Java 中,單元測試的最小單元是類。通過編寫針對類或方法的小段代碼,來檢驗被測代碼是否符合預期結果或行為。
執行單元測試可以幫助開發者驗證代碼是否正確實現了功能需求,以及是否能夠適應應用環境或需求變化。
二、Spring Boot 中的單元測試依賴
在 Spring Boot 項目中,要進行單元測試,首先需要添加相應的依賴。Maven 依賴如下:
<dependency><groupId>org.springframework.boot</groupId><artifactid>spring-boot-starter-test</artifactid><scope>test</scope>
</dependency>
這個依賴包含了多個庫和功能,主要有以下幾個:
JUnit
:JUnit 是 Java 中最流行和最常用的單元測試框架,它提供了一套 注解 和 斷言 來編寫和運行單元測試。例如 @Test 注解表示一個測試方法,assertEquals 斷言表示兩個值是否相等。Spring Test
:Spring Test 是一個基于 Spring 的測試框架,它提供了一套注解和工具來配置和管理 Spring 上下文和 Bean。例如 @SpringBootTest 注解表示一個集成測試類,@Autowired 注解表示自動注入一個 Bean。Mockito
:Mockito 是一個 Java 中最流行和最強大的 Mock 對象庫,它可以模仿復雜的真實對象行為,從而簡化測試過程。例如 @MockBean 注解表示創建一個 Mock 對象,when 方法表示定義 Mock 對象的行為。Hamcrest
:Hamcrest 是一個 Java 中的匹配器庫,它提供了一套語義豐富而易讀的匹配器來進行結果驗證。例如 asserThat 斷言表示驗證一個值是否滿足一個匹配器,is 匹配器表示兩個值是否相等。AssertJ
:AssertJ 是一個 Java 中的斷言庫,它提供了一套流暢而直觀的斷言語法來進行結果驗證。例如 assertThat 斷言表示驗證一個值是否滿足一個條件,isEqualTo 斷言表示兩個值是否相等。
除了以上這些庫外,spring-boot-starter-test 還包含了其他一些庫和功能,如 JsonPath、JsonAssert、XmlUnit 等。這些庫和功能可以根據不同的測試場景進行選擇和使用。
三、舉例 Spring Boot 中不同層次的單元測試
如果是通過spring initialize創建的springboot項目(本系列第一篇文章有講解),其實會自動創建一個單元測試類:
我們在寫單元測試的時候,直接繼承這個類即可。
3.1 Service層
在 Spring Boot 中,對 Service 層進行單元測試,可以使用 @SpringBootTest 注解來加載完整的 Spring 上下文,從而可以自動注入 Service 層的 Bean。同時,可以使用 @MockBean 注解來創建和注入其他層次的 Mock 對象,從而避免真實地調用其他層次的方法,而是模擬其行為。
例如,假設有一個 UserService 類,它提供了一個根據用戶 ID 查詢用戶信息的方法:
@Service
public class UserService {@Autowiredprivate UserRepository userRepository;public User getUserById(Long id) {return userRepository.findById(id).orElse(null);}
}
要對這個類進行單元測試,可以編寫以下測試類:
@SpringBootTest
public class UserServiceTest {@Autowiredprivate UserService userService;@MockBeanprivate UserRepository userRepository;@Testpublic void testGetUserById() {// 創建一個User對象User user = new User();user.setId(1L);user.setName("ACGkaka");user.setEmail("acgkaka@example.com");// 當調用userRepository.findById(1L)時,返回一個包含user對象的Optional對象when(userRepository.findById(1L)).thenReturn(Optional.of(user));// 調用userService.getUserId()方法,傳入1L作為參數,得到一個User對象。User result = userService.getUserById(1L);// 驗證結果對象與user對象相等assertThat(result).isEqualTo(user);// 驗證userRepository.findById(1L)方法被調用了一次verify(userRepository, times(1)).findById(1L);}
}
在這個測試類中,使用了以下幾個關鍵點和技巧:
- 使用
@SpringBootTest
注解表示加載完成的 Spring 上下文,并使用@Autowired
注解將 UserService 對象注入到測試類中。 - 使用
@MockBean
注解表示創建一個 UserRespository 對象,并使用@Autowired
注解將其注入到測試類中。這樣可以避免真實地調用 UserRepository 的方法,而是模擬其行為。 - 使用 when 方法來定義 Mock 對象的行為,例如當調用 userRepository.findById(1L) 時,返回一個包含 user 對象的 Optional 對象。
- 使用 userService.getUserById() 方法調用被測方法,得到一個 User 對象。
- 使用 AssertJ 的斷言語法來驗證結果對象與 user 對象是否相等。可以使用多種條件和匹配器來驗證結果。
- 使用 verify 方法來驗證 Mock 對象的方法是否被調用了指定次數。
3.2 Controller 層
Controller 層是指處理用戶請求和響應的層,它通常使用 @RestController
或 @Controller
注解來標識。在 Spring Boot 中,對 Controller 層進行單元測試,可以使用 @WebMvcTest 注解來啟動一個輕量級的 Spring MVC 上下文,只加載 Controller 層的組件。同時,可以使用 @AutoConfigureMockMvc
注解來自動配置一個 MockMvc 對象,用來模擬 Http 請求和驗證 Http 響應。
例如,假設有一個 UserController 類,它提供了一個根據用戶ID查詢用戶信息的接口:
@RestController
@RequestMapping("/users")
public class UserController {@Autowiredprivate UserService userService;@GetMapping("/{id}")public ResponseEntity<User> getUserById(@PathVariable Long id) {User user = userService.getUserById(id);if (user == null) {return ResponseEntity.notFound().build();} else {return ResponseEntity.ok(user);}}
}
要對這個類進行單元測試,可以編寫以下測試類:
@WebMvcTest(UserController.class)
@AutoConfigureMockMvc
public class UserControllerTest {@Autowiredprivate MockMvc mockMvc;@MockBeanprivate UserService userService;@Testpublic vid testGetUserById() throws Exception {// 創建一個 User 對象User user = new User();user.setId(1L);user.setName("ACGkaka");user.setEmail("acgkaka@example.com");// 當調用userService.getUserById(1L)時,返回user對象when(userService.getUserById(1L)).thenReturn(user);// 模擬發送GET請求到/users/1,并驗證響應狀態碼為200,響應內容為JSON格式的user對mockMvc.perform(get("/users/1"))mockMvc.perform(get("/users/1")).andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON)).andExpect(jsonPath("$.id").value(1L)).andExpect(jsonPath("$.name").value("ACGkaka")).andExpect(jsonPath("$.email").value("alice@example.com"));// 驗證userService.getUserById(1L)方法被調用了一次。verify(userSerivce, times(1)).getUserById(1L);}
}
在這個測試類中,使用了以下幾個關鍵點和技巧:
- 使用
@WebMvcTest(UserController.class)
注解表示只加載 UserController 類的組件,不加載其他層次的組件。 - 使用
@AutoConfigureMockMvc
注解表示自動配置一個 MockMvc 對象,并使用 @Autowired 注解將其注入到測試類中。 - 使用
@MockBean
注解表示創建一個 UserService 的 Mock 對象,并使用 @Autowired 注解將其注入到測試類中。這樣可以避免真實地調用 UserService 的方法,而是模擬其行為。 - 使用
when()
方法來定義 Mock 對象的行為,例如當調用 userService.getUserById(1L) 時,返回 user 對象。 - 使用
mockMvc.perform()
方法來模擬發送 Http 請求,并使用 andExpect 方法來驗證 Http 響應。可以使用多種匹配器來驗證響應狀態碼、內容類型、內容值等。 - 使用
verify()
方法來驗證 Mock 對象的方法是否被調用了指定次數。
3.3 Repository層
在 Spring Boot 中,對 Repository 層進行單元測試,可以使用 @DataJpaTest
注解來啟動一個嵌入式數據庫,并自動配置 JPA 相關的組件。同時,可以使用 @TestEntityManager 注解來獲取一個 TestEntityManager 對象,用來操作和驗證數據庫數據。
例如,假設有一個 UserRepository 接口,它繼承了 JpaRepository 接口,并提供了一個根據用戶姓名查詢用戶列表的方法:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {List<User> findByName(String name);
}
要對這個接口進行單元測試,可以編寫以下測試類:
@DataJpaTest
public class UserRepositoryTest {@Autowiredprivate UserRepository userRepository;@Autowiredprivate TestEntityManager testEntityManager;@Testpublic void testFindByName() {// 創建兩個User對象,并使用testEntityManager.persist方法將其保存到數據庫中User user1 = new User();user1.setName("Bob");user1.setEmail("bob@example.com");testEntityManager.persist(user1);User user2 = new User();user2.setName("Bob");user2.setEmail("bob2@example.com");testEntityManager.persist(user2);// 調用userRepository.findByName()方法,傳入“Bob”作為參數,得到一個用戶列表。List<User> users = userRepository.findByName("Bob");// 驗證用戶列表的大小為2,且包含了user1和user2assertThat(users).hasSize(2);assertThat(users).contains(user1, user2);}
}
在這個測試類中,使用了以下幾個關鍵點和技巧:
- 使用
@DataJpaTest
注解表示啟動一個嵌入式數據庫,并自動配置 JPA 相關的組件。這樣可以避免依賴外部數據庫,而是使用內存數據庫進行測試。 - 使用
@Autowired
注解將 UserRepository 和 TestEntityManager 對象注入到測試類中。 - 使用
testEntityManager.persist()
方法將 User 對象保存到數據庫中。這樣可以準備好測試數據,而不需要手動插入數據。 - 使用
userRepository.findByName()
方法調用自定義的查詢方法,得到一個用戶列表。 - 使用 AssertJ 的斷言語法來驗證用戶列表的大小和內容。可以使用多種條件和匹配器來驗證結果。
四、Spring Boot 中 Mock、Spy 對象的使用
4.1 使用Mock對象的背景
在 Spring Boot 中,除了使用 @WebMvcTest 和 @DataJpaTest 等注解來加載特定層次的組件外,還可以使用 @SpringBootTest
注解來加載完整的 Spring 上下文,從而進行更加集成的測試。但是,在這種情況下,可能會遇到一些問題,例如:
- 測試過程中需要依賴外部資源,如:數據庫、消息隊列、Web服務等。這些資源可能不穩定或不可用,導致測試失敗或超時。
- 測試過程中需要調用其他組件或服務的方法,但是這些方法的實現或行為不確定或不可控,導致測試結果不可預測或不準確。
- 測試過程中需要驗證一些難以觀察或測量的結果,如:日志輸出、異常拋出、私有變量值等。這些結果可能需要使用復雜或侵入式的方式來獲取或驗證。
為了解決這些問題,可以使用 Mock 對象來模擬真實對象行為。
4.2 什么是Mock對象,有哪些好處?
Mock 對象是指在測試過程中替代真實對象的虛擬對象,它可以根據預設的規則來返回特定的值或執行特定的操作。使用 Mock 對象有以下好處:
- 降低測試依賴: 通過使用 Mock 對象來替代外部資源或其他組件,可以減少測試過程中對真實環境的依賴,使得測試更加穩定和可靠。
- 提高測試控制: 通過使用 Mock 對象來模擬特定的行為或場景,可以提高測試過程中對真實對象行為的控制,使得測試更加靈活和精確。
- 簡化測試驗證: 通過使用 Mock 對象來返回特定的結果或觸發特定的事件,可以簡化測試過程中對真實對象結果或事件的驗證,使得測試更加簡單和直觀。
4.3 使用 Mock 對象的示例
在 Spring Boot 中,要使用 Mock 對象,可以使用 @MockBean
注解來創建和注入一個 Mock 對象。這個注解會自動使用 Mockito
庫來創建一個 Mock 對象,并將其添加到 Spring 上下文中。同時,可以使用 when() 方法來定義 Mock 對象的行為,以及 verify() 方法來驗證 Mock 對象的方法調用。
例如,假設有一個 EmailService 接口,它提供了一個發送郵件的方法:
public interface EmailService {void sendEmail(String to, String subject, String content);
}
要對這個接口進行單元測試,可以編寫以下測試類:
@SpringBootTest
public class EmailServiceTest {@Autowiredprivate UserService userService;@MockBeanprivate EmailService emailService;@Testpublic void testSendEmail() {// 創建一個User對象User user = new User();user.setId(1L);user.setName("ACGkaka");user.setEmail("acgkaka@example.com");// 當調用emailService.sendEmail方法時,什么也不做doNothing().when(emailService).sendEmail(anyString(), anyString(), anyString());// 調用userService.sendWelcomeEmail方法,傳入user對象作為參數userService.sendWelcomeEmail(user);// 驗證emailService.sendEmail方法被調用了一次,并且參數分別為user.getEmail()、"Welcome"、"Hello, ACGkaka"verify(emailService, times(1)).sendEmail(user.getEmail(), "Welcom", "Hello, ACGkaka");}
}
在這個測試類中,使用了以下幾個關鍵點和技巧:
- 使用
@SpringBootTest
注解表示加載完整的 Spring 上下文,并使用 @Autowired 注解將 UserService 對象注入到測試類中。 - 使用
@MockBean
注解表示創建一個 EmailService 的 Mock 對象,并使用 @Autowired 注解將其注入到測試類中。這樣可以避免真實地調用 EmailService 的方法,而是模擬其行為。 - 使用
doNothing()
方法來定義 Mock 對象的行為,例如當調用 emailService.sendEmail() 方法時,什么也不做。也可以使用 doReturn()、doThrow()、doAnswer() 等方法來定義其他類型的行為。 - 使用
anyString()
方法來表示任意字符串類型的參數,也可以使用 anyInt、anyLong、anyObject 等方法來表示其他類型的參數。 - 使用 userService.sendEmail() 方法調用被測方法,傳入user對象作為參數。
- 使用
verify()
方法來驗證 Mock 對象的方法是否被調用了指定次數,并且參數是否符合預期。也可以使用 never、atLeast、atMost 等方法來表示其他次數的驗證。
4.4 什么是Spy對象,有哪些好處?
除了使用 @MockBean 注解來創建和注入 Mock 對象外,還可以使用 @SpyBean
注解來創建和注入 Spy 對象。Spy 對象是指在測試u工程中部分替代真實對象的虛擬對象,它可以根據預設的規則來返回特定的值或執行特定的操作,同時保留真實對象的其他行為。使用 Spy 對象有以下好處:
- 保留真實行為: 通過使用 Spy 對象來替代真實對象,可以保留真實對象的其他行為,使得測試更加接近真實環境。
- 修改部分行為: 通過使用 Spy 對象來模擬特定的行為或場景,可以修改真實對象的部分行為,使得測試更加靈活和精確。
- 觀察真實結果: 通過使用 Spy 對象來返回特定的結果或觸發特定的事件,可以觀察真實對象的結果或事件,使得測試更加直觀和可信。
4.5 使用 Spy 對象的示例
在 Spring Boot 中,要使用 Spy 對象,可以使用 @SpyBean
注解來創建和注入一個 Spy 對象。這個注解會自動使用 Mockito 庫來創建一個 Spy 對象,并將其添加到 Spring 上下文中。同時,可以使用 when() 方法來定義 Spy 對象的行為,以及 verify() 方法來驗證 Spy 對象的方法調用。
例如,假設有一個 LogService 接口,它提供了一個記錄日志的方法:
public interface LogService {void log(String message);
}
要對這個接口進行單元測試,可以編寫以下測試類:
@SpringBootTest
public class LogServiceTest {@Autowiredprivate UserService userService;@SpyBeanprivate LogService logService;@Testpublic void testLog() {// 創建一個User對象User user = new User();user.setId(1L);user.setName("Alice");user.setEmail("alice@example.com");// 當調用logService.log方法時,調用真實的方法,并打印參數到控制臺doAnswer(invocation -> {String message = invocation.getArgument(0);System.out.println(message);invocation.callRealMethod();return null;}).when(logService).log(anyString());// 調用userService.createUser方法,傳入user對象作為參數userService.createUser(user);// 驗證logService.log方法被調用了兩次,并且參數分別為"Creating user: Alice"、"User created: Alice"verify(logService, times(2)).log(anyString());verify(logService, times(1)).log("Creating user: Alice");verify(logService, times(1)).log("User created: Alice");}
}
在這個測試類中,使用了以下幾個關鍵點和技巧:
- 使用
@SpringBootTest
注解表示加載完整的Spring上下文,并使用@Autowired注解將UserService對象注入到測試類中。 - 使用
@SpyBean
注解表示創建一個LogService的Spy對象,并使用@Autowired注解將其注入到測試類中。這樣可以保留LogService的真實行為,同時修改部分行為。 - 使用
doAnswer()
方法來定義Spy對象的行為,例如當調用logService.log方法時,調用真實的方法,并打印參數到控制臺。也可以使用doReturn、doThrow、doNothing等方法來定義其他類型的行為。 - 使用
anyString()
方法來表示任意字符串類型的參數。也可以使用anyInt、anyLong、anyObject等方法來表示其他類型的參數。 - 使用 userService.createUser() 方法調用被測方法,傳入user對象作為參數。
- 使用
verify()
方法來驗證Spy對象的方法是否被調用了指定次數,并且參數是否符合預期。也可以使用never()、atLeast()、atMost() 等方法來表示其他次數的驗證。
整理完畢,完結撒花~🌻
參考地址:
1.Spring Boot中如何編寫優雅的單元測試,https://blog.csdn.net/TaloyerG/article/details/132487310
2.【快學springboot】在springboot中寫單元測試,https://cloud.tencent.com/developer/article/2385462