深度揭秘JUnit5與Mockito的單元測(cè)試神秘面紗
在今天的學(xué)習(xí)中,我們將深入研究JUnit和Mockito,這是Java開(kāi)發(fā)中最強(qiáng)大的單元測(cè)試工具之一。通過(guò)學(xué)習(xí)如何編寫(xiě)清晰、高效的單元測(cè)試,我們將揭開(kāi)單元測(cè)試的神秘面紗,助力你在項(xiàng)目中寫(xiě)出更健壯的代碼。
提示: 今天的代碼是在第九天代碼的基礎(chǔ)上進(jìn)行開(kāi)發(fā),我們將為UserController中添加更多的單元測(cè)試方法,以展示JUnit和Mockito的強(qiáng)大功能。
核心知識(shí)介紹:
Unit 5 的主要特性和注解:@Test:標(biāo)記方法作為測(cè)試方法。@BeforeEach / @AfterEach:分別表示在每個(gè)測(cè)試方法前后運(yùn)行的方法。@BeforeAll / @AfterAll:分別表示在所有測(cè)試開(kāi)始之前和所有測(cè)試結(jié)束之后只運(yùn)行一次的方法。@DisplayName:為測(cè)試類(lèi)或測(cè)試方法定義一個(gè)自定義的顯示名稱(chēng)。@Nested:表示內(nèi)部類(lèi),其成員方法可以作為嵌套的測(cè)試類(lèi)進(jìn)行分組。@Tag:為測(cè)試方法添加標(biāo)簽,可以用來(lái)過(guò)濾測(cè)試執(zhí)行。@ExtendWith:用來(lái)注冊(cè)自定義擴(kuò)展,例如可以用來(lái)集成 Spring TestContext Framework。@Disabled:禁用某個(gè)測(cè)試方法或類(lèi)。
JUnit 5 斷言和假設(shè):Assertions 類(lèi)提供了一系列的靜態(tài)方法來(lái)聲明斷言,如 assertEquals, assertTrue, assertAll 等。Assumptions 類(lèi)提供了靜態(tài)方法來(lái)聲明測(cè)試的前提條件,如 assumeTrue。Mockito 的主要特性和注解:@Mock:創(chuàng)建一個(gè)模擬對(duì)象。@InjectMocks:自動(dòng)注入模擬對(duì)象到被測(cè)試的類(lèi)中。@Spy:創(chuàng)建一個(gè)真實(shí)對(duì)象的包裝,可以模擬某些方法的行為。@Captor:創(chuàng)建一個(gè)參數(shù)捕獲器,用于捕獲方法調(diào)用的參數(shù)。
@TestMethodOrder 是一個(gè)類(lèi)型級(jí)別的注解,用于指定測(cè)試類(lèi)中測(cè)試方法的執(zhí)行順序。它需要與 MethodOrderer 接口的實(shí)現(xiàn)類(lèi)一起使用,JUnit 提供了幾種不同的方法排序器,如按名稱(chēng)、注解、隨機(jī)等。
@Order 是一個(gè)方法級(jí)別的注解,用于指定測(cè)試方法的執(zhí)行順序。當(dāng)測(cè)試類(lèi)上使用了 @TestMethodOrder(OrderAnnotation.class) 注解時(shí),你可以在每個(gè)測(cè)試方法上使用 @Order 來(lái)定義它們的執(zhí)行順序。
以下是一些常用的 MethodOrderer 實(shí)現(xiàn):
OrderAnnotation:根據(jù)測(cè)試方法上的 @Order 注解來(lái)指定執(zhí)行順序。測(cè)試方法通過(guò) @Order 注解的值(一個(gè)整數(shù))來(lái)定義它們的執(zhí)行順序。Alphanumeric:按照測(cè)試方法名稱(chēng)的字母數(shù)字順序執(zhí)行。這個(gè)順序首先考慮數(shù)字,然后是字母。MethodName:按照方法名稱(chēng)的字典順序(即字符串順序)執(zhí)行。Random:每次執(zhí)行時(shí)都按照隨機(jī)順序執(zhí)行測(cè)試方法。這有助于發(fā)現(xiàn)由于測(cè)試方法間的依賴關(guān)系而產(chǎn)生的潛在問(wèn)題。DisplayName:按照測(cè)試方法的顯示名稱(chēng)(@DisplayName 注解指定的值)的字典順序執(zhí)行。
代碼示例:
在今天的代碼示例中,我們將在昨天的基礎(chǔ)上進(jìn)一步完善UserController的單元測(cè)試,使用JUnit和Mockito來(lái)驗(yàn)證控制器層的方法是否按照預(yù)期執(zhí)行。
在 pom.xml 文件增加增加測(cè)試依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>3.1.6</version>
<!-- 排除 JUnit 4 -->
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<version>2.2</version>
</dependency>
UserControllerTest.java
package com.icoderoad.springboot60days.day9.controller;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.*;
import static org.mockito.ArgumentMatchers.any;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.icoderoad.springboot60days.day9.entity.User;
import com.icoderoad.springboot60days.day9.service.UserService;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import java.util.Arrays;
import java.util.List;
@ExtendWith(MockitoExtension.class)
@WebMvcTest(UserController.class)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Autowired
private ObjectMapper objectMapper;
private User user;
@BeforeEach
void setUp() {
user = new User();
user.setId(1L);
user.setUsername("Test User");
user.setEmail("test@example.com");
}
/**
* 驗(yàn)證UserController的getAllUsers方法正常獲取所有用戶信息。
*/
@Test
@Order(4)
public void getAllUsersTest() throws Exception {
List<User> users = Arrays.asList(user);
when(userService.list()).thenReturn(users);
mockMvc.perform(get("/users"))
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(1)))
.andExpect(jsonPath("$[0].username", is(user.getUsername())));
}
/**
* 驗(yàn)證UserController的createUser方法正常創(chuàng)建用戶。
*/
@Test
@Order(1)
public void createUserTest() throws Exception {
when(userService.saveOrUpdate(any(User.class))).thenReturn(true);;
mockMvc.perform(post("/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(user)))
.andExpect(status().isOk());
verify(userService, times(1)).save(any(User.class));
}
/**
* 驗(yàn)證UserController的getUserById方法正常獲取指定ID的用戶信息。
*/
@Test
@Order(2)
public void getUserByIdTest() throws Exception {
when(userService.getById(user.getId())).thenReturn(user);
mockMvc.perform(get("/users/{id}", user.getId()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.username", is(user.getUsername())));
}
/**
* 驗(yàn)證UserController的updateUserById方法正常更新指定ID的用戶信息。
*/
@Test
@Order(3)
public void updateUserByIdTest() throws Exception {
when(userService.saveOrUpdate(any(User.class))).thenReturn(true);;
mockMvc.perform(put("/users/{id}", user.getId())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(user)))
.andExpect(status().isOk());
verify(userService, times(1)).updateById(any(User.class));
}
/**
* 驗(yàn)證UserController的deleteUserById方法正常刪除指定ID的用戶。
*/
@Test
@Order(5)
public void deleteUserByIdTest() throws Exception {
when(userService.removeById(user.getId())).thenReturn(true);;
mockMvc.perform(delete("/users/{id}", user.getId()))
.andExpect(status().isOk());
verify(userService, times(1)).removeById(user.getId());
}
}
當(dāng)天學(xué)習(xí)知識(shí)總結(jié):
在今天的學(xué)習(xí)中,我們深入研究了單元測(cè)試,并利用 Mockito 框架加強(qiáng)了測(cè)試的功能。通過(guò)學(xué)習(xí)如何編寫(xiě)JUnit5測(cè)試以及使用Mockito模擬依賴,我們揭開(kāi)了單元測(cè)試的神秘面紗,為更健壯的代碼打下了堅(jiān)實(shí)的基礎(chǔ)。
在代碼示例中,我們創(chuàng)建了一個(gè) UserControllerTest 類(lèi),使用了 Mockito 注解和特性。主要注解包括 @Mock 用于創(chuàng)建模擬對(duì)象,@InjectMocks 用于創(chuàng)建被測(cè)試類(lèi)的實(shí)例并自動(dòng)注入模擬對(duì)象,@Spy 用于創(chuàng)建 Spy 對(duì)象,@Captor 用于捕獲方法參數(shù),以及 @RunWith(MockitoJUnitRunner.class) 用于在 JUnit 測(cè)試中運(yùn)行 Mockito 測(cè)試。
通過(guò)這些注解和特性,我們能夠編寫(xiě)清晰、高效的單元測(cè)試,驗(yàn)證控制器層的各個(gè)方法的行為是否符合預(yù)期。其中,我們測(cè)試了獲取所有用戶、創(chuàng)建用戶、獲取指定ID用戶、更新用戶、刪除用戶等方法,以確保它們?cè)诓煌闆r下能夠正確執(zhí)行。
總體而言,通過(guò)今天的學(xué)習(xí),我們不僅深入了解了單元測(cè)試的基本原理,還學(xué)會(huì)了如何在Spring Boot項(xiàng)目中使用JUnit5和Mockito框架進(jìn)行測(cè)試,為后續(xù)更復(fù)雜的業(yè)務(wù)邏輯和代碼改動(dòng)提供了可靠的測(cè)試基礎(chǔ)。在接下來(lái)的學(xué)習(xí)中,我們將繼續(xù)