Springboot結(jié)合Junit單元測(cè)試的實(shí)現(xiàn)
一、junit4與junit5及springboot中的使用
在現(xiàn)代軟件開(kāi)發(fā)中,單元測(cè)試是確保代碼質(zhì)量的重要環(huán)節(jié)。Spring Boot框架通過(guò)整合JUnit,為開(kāi)發(fā)者提供了便捷的單元測(cè)試支持。
1.1 Spring Boot中JUnit版本的變化
在Spring Boot 2.0之前,框架默認(rèn)使用JUnit 4作為測(cè)試平臺(tái)。然而,從Spring Boot 2.0開(kāi)始,JUnit 5成為默認(rèn)的測(cè)試框架。以下是Spring Boot不同版本中JUnit版本的對(duì)比:
| Spring Boot版本 | 默認(rèn)JUnit版本 |
|---|---|
| 1.x | JUnit 4 |
| 2.x | JUnit 5 |
例如,Spring Boot 2.2.0使用JUnit 5.5.2版本。開(kāi)發(fā)者可以通過(guò)POM文件確認(rèn)具體版本。
1.2 POM文件配置
在Spring Boot項(xiàng)目中,單元測(cè)試的依賴通過(guò)spring-boot-starter-test啟動(dòng)器引入。以下是POM文件的配置示例:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>1.3 排除JUnit Vintage Engine
junit-vintage-engine是JUnit 3和JUnit 4的運(yùn)行支持平臺(tái)。默認(rèn)情況下,Spring Boot測(cè)試啟動(dòng)器會(huì)排除該依賴,以鼓勵(lì)開(kāi)發(fā)者使用JUnit 5。如果需要使用JUnit 4,可以移除<exclusions>標(biāo)簽。
二、JUnit 4與JUnit 5的對(duì)比
以下是JUnit 4和JUnit 5的主要差異:
| 特性 | JUnit 4 | JUnit 5 |
|---|---|---|
| 注解 | @RunWith、@Test | @SpringBootTest、@Test |
| 默認(rèn)啟動(dòng)類支持 | 需要手動(dòng)指定啟動(dòng)類 | 自動(dòng)檢測(cè)啟動(dòng)類 |
| 測(cè)試方法支持 | 需要@Test注解 | 需要@Test注解 |
| 擴(kuò)展支持 | 有限 | 更強(qiáng)大的擴(kuò)展機(jī)制 |
三、JUnit 4測(cè)試代碼示例
以下是一個(gè)基于JUnit 4的測(cè)試代碼示例:
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.junit.Test;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = SpringBootExceptionAndJourneyApplication.class)
public class UserServiceTest {
@Test
public void testAddUser() {
System.out.println("JUnit 4 測(cè)試方法運(yùn)行成功!");
}
}四、JUnit 5測(cè)試代碼示例
以下是一個(gè)基于JUnit 5的測(cè)試代碼示例:
import org.springframework.boot.test.context.SpringBootTest;
import org.junit.jupiter.api.Test;
@SpringBootTest
public class UserServiceTest {
@Test
public void testAddUser() {
System.out.println("JUnit 5 測(cè)試方法運(yùn)行成功!");
}
}
五、實(shí)際案例:持久層與業(yè)務(wù)層測(cè)試
假設(shè)我們有一個(gè)UserDAO和UserService,以下是它們的實(shí)現(xiàn)代碼:
5.1 持久層代碼
public class UserDAOImpl {
public void insert() {
System.out.println("INSERT INTO USER VALUES(...)");
}
}5.2 業(yè)務(wù)層代碼
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl {
private final UserDAOImpl userDAO;
public UserServiceImpl(UserDAOImpl userDAO) {
this.userDAO = userDAO;
}
public void addUser() {
userDAO.insert();
}
}5.3 測(cè)試代碼
import org.springframework.boot.test.context.SpringBootTest;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
@SpringBootTest
public class UserServiceTest {
@Autowired
private UserServiceImpl userService;
@Test
public void testAddUser() {
userService.addUser();
}
}六、常見(jiàn)問(wèn)題與解答
以下是關(guān)于Spring Boot整合JUnit單元測(cè)試的常見(jiàn)問(wèn)題:
| 問(wèn)題 | 答案 |
|---|---|
| 1. Spring Boot默認(rèn)使用哪個(gè)JUnit版本? | Spring Boot 2.x默認(rèn)使用JUnit 5。 |
| 2. 如何在Spring Boot中使用JUnit 4? | 需要移除POM文件中junit-vintage-engine的排除配置。 |
| 3. JUnit 5是否需要指定啟動(dòng)類? | 默認(rèn)情況下,JUnit 5會(huì)自動(dòng)檢測(cè)啟動(dòng)類。如果項(xiàng)目中有多個(gè)啟動(dòng)類,需要通過(guò)@SpringBootTest(classes = ...)指定。 |
| 4. JUnit 5的測(cè)試方法是否需要@RunWith注解? | 不需要,JUnit 5使用@SpringBootTest注解即可。 |
| 5. 如何確認(rèn)JUnit版本? | 通過(guò)POM文件中的依賴版本確認(rèn),例如junit-jupiter-api的版本號(hào)。 |
七、流程圖:Spring Boot整合JUnit單元測(cè)試的步驟
創(chuàng)建Spring Boot項(xiàng)目添加spring-boot-starter-test依賴確認(rèn)JUnit版本編寫(xiě)測(cè)試代碼運(yùn)行測(cè)試

第一部分總結(jié)
我們現(xiàn)在直接使用junit5在springboot項(xiàng)目中使用即可。使用方式為添加注解@SpringBootTest(會(huì)自己找啟動(dòng)類,有多個(gè)則通過(guò)classes = ...指定)就可以讓容器啟動(dòng)來(lái)測(cè)試。
疑問(wèn)1:不是說(shuō)5的話boot項(xiàng)目需要ext注解嗎?
AI解答: @ExtendWith 確實(shí)是 JUnit 5 的核心注解。
不過(guò),在 Spring Boot 項(xiàng)目里,你通常不需要手動(dòng)添加它。
?? 為什么會(huì)有這個(gè)誤解?
你的理解是對(duì)的:JUnit 5 確實(shí)使用 @ExtendWith 來(lái)集成 Spring 的功能,它取代了 JUnit 4 的 @RunWith。
在早期版本的 Spring Boot (2.1.x 之前),確實(shí)需要手動(dòng)在測(cè)試類上寫(xiě)上 @ExtendWith(SpringExtension.class)。
? 現(xiàn)在的實(shí)際情況是怎樣的?
從 Spring Boot 2.1.x 版本開(kāi)始,官方為了讓開(kāi)發(fā)更便捷,做了一個(gè)“貼心”的優(yōu)化。
@SpringBootTest 這個(gè)我們最常用的注解,其內(nèi)部源碼已經(jīng)自動(dòng)包含了 @ExtendWith(SpringExtension.class)。
你可以把它理解為:@SpringBootTest 是一個(gè)“大禮包”,里面已經(jīng)幫你把 @ExtendWith 這個(gè)“工具”放進(jìn)去了。所以,當(dāng)你使用 @SpringBootTest 時(shí),就無(wú)需再重復(fù)添加了。
代碼對(duì)比
早期寫(xiě)法 (已過(guò)時(shí))
// 在 Spring Boot 2.1 之前,需要這樣寫(xiě) @ExtendWith(SpringExtension.class) // 手動(dòng)啟用 Spring 擴(kuò)展 @SpringBootTest public class OrderServiceTest { // ... }現(xiàn)在寫(xiě)法 (推薦) // 現(xiàn)在,這樣寫(xiě)就足夠了! @SpringBootTest public class OrderServiceTest { // ... }
?? 什么時(shí)候才需要手動(dòng)寫(xiě)@ExtendWith?
只有在一些不使用 @SpringBootTest 的底層測(cè)試場(chǎng)景中,你才可能需要手動(dòng)添加。
例如,如果你只用 @ContextConfiguration 來(lái)加載一個(gè)特定的 Spring 配置文件進(jìn)行測(cè)試,這時(shí)就需要手動(dòng)通過(guò) @ExtendWith(SpringExtension.class) 來(lái)啟用 Spring 的擴(kuò)展支持。
但對(duì)于絕大多數(shù) Spring Boot 的集成測(cè)試來(lái)說(shuō),放心地只使用 @SpringBootTest 吧!
疑問(wèn)2:有必要每次啟動(dòng)整個(gè)容器嗎?請(qǐng)看第二部分
第二部分 springboot項(xiàng)目怎么進(jìn)行mock
很多開(kāi)發(fā)者在剛開(kāi)始寫(xiě)測(cè)試時(shí),都會(huì)糾結(jié)是“啟動(dòng)整個(gè) Spring 容器”還是“只測(cè)一個(gè)類”。
簡(jiǎn)單來(lái)說(shuō), “優(yōu)雅”的測(cè)試 = 選對(duì)測(cè)試類型 + 用好 Mock 隔離
?? 一、Mock 是啥?解決啥問(wèn)題?
1. 通俗解釋:什么是 Mock?
想象一下,你是一名汽車(chē)引擎設(shè)計(jì)師(你在測(cè)試 UserService),你需要測(cè)試引擎(UserService)的運(yùn)轉(zhuǎn)是否良好。
如果不使用 Mock:你必須把引擎裝到整輛車(chē)?yán)?,連上油箱、排氣管、輪胎,甚至要把車(chē)開(kāi)到路上(啟動(dòng) Spring 容器、連接真實(shí)數(shù)據(jù)庫(kù)、連接真實(shí) Redis)。這非常慢,而且如果車(chē)打不著火,你不知道是引擎壞了,還是油箱漏了,還是輪胎沒(méi)氣。
使用 Mock:你在實(shí)驗(yàn)室里,給引擎接上一個(gè)模擬油箱(Mock Repository)和一個(gè)模擬排氣管(Mock EmailService)。
- 你可以控制“模擬油箱”里有多少油(Stubbing:預(yù)設(shè)返回值)。
- 你可以觀察引擎是否真的向“模擬排氣管”排氣了(Verification:驗(yàn)證調(diào)用)。
- 重點(diǎn):你只測(cè)試引擎本身,不關(guān)心外面的世界。
2. Mock 解決了什么問(wèn)題?
- 速度極快:不需要啟動(dòng) Spring 容器,不需要連接數(shù)據(jù)庫(kù)(IO 操作最耗時(shí))。單元測(cè)試通常是毫秒級(jí)的。
- 隔離性強(qiáng):如果測(cè)試失敗了,肯定是你的
Service邏輯寫(xiě)錯(cuò)了,而不是因?yàn)閿?shù)據(jù)庫(kù)連不上,或者網(wǎng)絡(luò)波動(dòng)。 - 覆蓋極端情況:你可以輕松模擬“數(shù)據(jù)庫(kù)掛了”或者“查不到數(shù)據(jù)”的場(chǎng)景,而在真實(shí)環(huán)境中很難故意制造這些故障。
? 二、如何“優(yōu)雅”地執(zhí)行 Boot 項(xiàng)目單元測(cè)試?
在 Spring Boot 中,優(yōu)雅的核心在于 “各司其職” 。不要把所有測(cè)試都寫(xiě)成 @SpringBootTest(啟動(dòng)全容器),那樣太慢了。
我們需要區(qū)分兩種測(cè)試策略:
1. 純單元測(cè)試 (Unit Test) —— 推薦用于 Service 層
特點(diǎn):完全不啟動(dòng) Spring 容器,純 Java 代碼運(yùn)行。
工具:JUnit 5 + Mockito (@Mock, @InjectMocks)。
場(chǎng)景:測(cè)試 UserService 里的業(yè)務(wù)邏輯(比如計(jì)算價(jià)格、校驗(yàn)參數(shù))。
代碼示例:
// 1. 不需要 @SpringBootTest,不需要啟動(dòng)容器!
// 使用 Mockito 的擴(kuò)展來(lái)初始化 Mock 對(duì)象
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
// 2. @Mock: 創(chuàng)建一個(gè)假的 UserRepository,它是空的,需要你喂數(shù)據(jù)
@Mock
private UserRepository userRepository;
// 3. @InjectMocks: 創(chuàng)建 UserService 實(shí)例,并把上面的 userRepository 塞進(jìn)去
@InjectMocks
private UserService userService;
@Test
void shouldFindUserById() {
// --- Arrange (準(zhǔn)備) ---
User mockUser = new User(1L, "Alice");
// 告訴 Mock 對(duì)象:當(dāng)有人調(diào)用 findById(1L) 時(shí),返回 mockUser
when(userRepository.findById(1L)).thenReturn(Optional.of(mockUser));
// --- Act (執(zhí)行) ---
User result = userService.findById(1L);
// --- Assert (斷言) ---
assertThat(result.getName()).isEqualTo("Alice");
// --- Verify (驗(yàn)證) ---
// 驗(yàn)證 userRepository.findById 是否真的被調(diào)用了一次
verify(userRepository, times(1)).findById(1L);
}
}優(yōu)雅點(diǎn):速度飛快,完全隔離。
2. 切片測(cè)試 / 集成測(cè)試 (Slice Test) —— 推薦用于 Controller 或 Repository
特點(diǎn):只啟動(dòng) Spring 容器的一部分(比如只啟動(dòng) Web 層,或者只啟動(dòng) JPA 層)。
工具:@WebMvcTest (控制器), @DataJpaTest (數(shù)據(jù)庫(kù)), @MockBean。
場(chǎng)景:測(cè)試 UserController 的接口映射是否正確,或者測(cè)試 SQL 語(yǔ)句是否正確。
代碼示例 (測(cè)試 Controller) :
// 1. @WebMvcTest: 只啟動(dòng) Web 層相關(guān)的 Bean (Controller, Converter 等),不啟動(dòng) Service
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc; // Spring 提供的模擬 HTTP 客戶端
// 2. @MockBean: 這是 Spring 的注解!
// 它會(huì)去 Spring 容器里,把 UserService 替換成一個(gè) Mock 對(duì)象
@MockBean
private UserService userService;
@Test
void shouldReturnUserJson() throws Exception {
// --- Arrange ---
// 模擬 Service 層返回?cái)?shù)據(jù)
when(userService.findById(1L)).thenReturn(new User(1L, "Alice"));
// --- Act & Assert ---
// 發(fā)送一個(gè)模擬的 GET 請(qǐng)求
mockMvc.perform(get("/users/1"))
.andExpect(status().isOk()) // 期望狀態(tài)碼 200
.andExpect(jsonPath("$.name").value("Alice")); // 期望返回 JSON 中有 name: Alice
}
}優(yōu)雅點(diǎn):比 @SpringBootTest 快,但又能測(cè)試 Spring 的注解(如 @RestController, @RequestMapping)是否生效。
?? 三、總結(jié):Mock 注解對(duì)比表
這是最容易混淆的地方,請(qǐng)注意區(qū)分:
表格
| 特性 | @Mock(Mockito) | @MockBean(Spring Boot) |
|---|---|---|
| 所屬庫(kù) | Mockito | Spring Boot Test |
| 是否啟動(dòng) Spring | 否 (純單元測(cè)試) | 是 (集成測(cè)試/切片測(cè)試) |
| 作用范圍 | 僅在測(cè)試類內(nèi)部有效 | 會(huì)替換 Spring 容器中的 Bean |
| 使用場(chǎng)景 | 測(cè)試 Service 業(yè)務(wù)邏輯 | 測(cè)試 Controller, 或者需要 Spring 注入的場(chǎng)景 |
| 性能 | 極快 (毫秒級(jí)) | 較快 (秒級(jí),取決于加載多少組件) |
?? 四、最佳實(shí)踐建議
- Service 層:優(yōu)先使用 @ExtendWith(MockitoExtension.class) + @Mock。不要?jiǎng)硬粍?dòng)就 @SpringBootTest,那樣太慢了。
- Controller 層:使用 @WebMvcTest + @MockBean。
- Repository 層:使用 @DataJpaTest (它會(huì)自動(dòng)配置內(nèi)存數(shù)據(jù)庫(kù) H2)。
- 全鏈路測(cè)試:只有當(dāng)你需要測(cè)試“整個(gè)應(yīng)用能不能跑起來(lái)”或者“配置類是否正確”時(shí),才使用 @SpringBootTest。
這樣分層測(cè)試,你的項(xiàng)目構(gòu)建速度會(huì)非常快,而且邏輯清晰,維護(hù)起來(lái)也很優(yōu)雅。
第二部分總結(jié)
使用mock可以最小化范圍測(cè)試,而不是啟動(dòng)整個(gè)容器。一般測(cè)試的都是service層,直接使用@ExtendWith(MockitoExtension.class) + @Mock。不要?jiǎng)硬粍?dòng)就 @SpringBootTest,那樣太慢了
第三部分 Mock中的常見(jiàn)問(wèn)題
一、mock原理
就是 “偽造” 依賴接口 / 對(duì)象 / 函數(shù)的返回結(jié)果,讓程序在沒(méi)有真實(shí)后端、真實(shí)服務(wù)時(shí)也能正常跑、正常測(cè)。原理為通過(guò)動(dòng)態(tài)代理、字節(jié)碼增強(qiáng)或請(qǐng)求攔截等方式,劫持目標(biāo)方法 / 接口調(diào)用,跳過(guò)真實(shí)邏輯執(zhí)行并直接返回預(yù)設(shè)偽造數(shù)據(jù),從而實(shí)現(xiàn)依賴隔離與行為模擬。
? 疑問(wèn):final類怎么模擬呢?
在 Java 中,final 關(guān)鍵字的設(shè)計(jì)初衷就是為了防止繼承(類)或重寫(xiě)(方法)。而 Mockito 的核心原理恰恰是生成子類(動(dòng)態(tài)代理)來(lái)攔截方法調(diào)用。 所以,默認(rèn)情況下,Mockito 無(wú)法 Mock final 類或 final 方法。如果你強(qiáng)行去 mock,通常會(huì)報(bào) Cannot mock/spy class ... final class 的錯(cuò)誤。
?? 方案:使用mockito-inline(推薦,現(xiàn)代做法)
這是目前最主流的做法。從 Mockito 2.x 后期版本開(kāi)始,官方提供了一個(gè)擴(kuò)展模塊 mockito-inline,它利用 Java Instrumentation API 在運(yùn)行時(shí)修改字節(jié)碼,從而支持 Mock final 類。
適用場(chǎng)景:Spring Boot 2.x (較新版本) 或 Spring Boot 3.x,且你不想引入沉重的 PowerMock。
1. 添加依賴
雖然 Spring Boot 的 spring-boot-starter-test 已經(jīng)包含了 mockito-core,但你需要額外引入 mockito-inline。
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>5.x.x</version> <!-- 版本號(hào)通常與 mockito-core 保持一致 -->
<scope>test</scope>
</dependency>2. 開(kāi)啟配置(關(guān)鍵步驟)
僅僅加依賴是不夠的,你必須告訴 Mockito 使用這個(gè)“內(nèi)聯(lián)”模式。
在 src/test/resources 目錄下創(chuàng)建一個(gè)文件夾 mockito-extensions,并在其中創(chuàng)建一個(gè)文件 org.mockito.plugins.MockMaker。
文件路徑:src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
文件內(nèi)容:
mock-maker=inline

3. 編寫(xiě)測(cè)試
配置好后,你就可以像 Mock 普通類一樣 Mock final 類了,代碼完全不用變:
// 假設(shè) FinalService 是一個(gè) final 類
final class FinalService {
public String sayHello() { return "Hello"; }
}
@ExtendWith(MockitoExtension.class)
class FinalServiceTest {
@Mock
private FinalService finalService; // 直接 @Mock,不會(huì)報(bào)錯(cuò)!
@Test
void testFinalClass() {
when(finalService.sayHello()).thenReturn("Mocked Hello");
assertEquals("Mocked Hello", finalService.sayHello());
}
}二、?? Spy 是啥?解決什么問(wèn)題?
1. 核心概念:部分模擬
Mock(完全模擬) :創(chuàng)建一個(gè)空殼對(duì)象。所有方法默認(rèn)都不執(zhí)行真實(shí)代碼,直接返回 null 或 0。你必須手動(dòng)定義每一個(gè)方法的行為。
Spy(部分模擬) :包裝一個(gè)真實(shí)的對(duì)象。
- 默認(rèn)情況下,它會(huì)執(zhí)行真實(shí)的代碼。
- 只有當(dāng)你明確告訴它“這個(gè)方法要攔截”時(shí),它才會(huì)返回假數(shù)據(jù)。
2. 解決什么問(wèn)題?
- 場(chǎng)景一:遺留代碼或復(fù)雜對(duì)象。當(dāng)你有一個(gè)類,方法很多,你只想 Mock 其中一個(gè)很難測(cè)的方法(比如調(diào)用了外部 API),而其他方法邏輯很復(fù)雜你不想重寫(xiě),這時(shí)用 Spy 最省事。
- 場(chǎng)景二:驗(yàn)證真實(shí)調(diào)用。你想確保某個(gè)方法被調(diào)用了,同時(shí)還想驗(yàn)證它執(zhí)行后的真實(shí)副作用。
??? 怎么用?(核心語(yǔ)法)
在 Spring Boot 項(xiàng)目中,我們通常分兩種情況使用 Spy:純單元測(cè)試 和 Spring 容器集成測(cè)試。
1. 純單元測(cè)試(使用@Spy)
這是 Mockito 的原生用法,用于測(cè)試普通的 Java 類。
關(guān)鍵點(diǎn):使用 Spy 時(shí),存根語(yǔ)法(Stubbing)必須換!
- Mock 用:
when(mock.method()).thenReturn(...) - Spy 用:
doReturn(...).when(spy).method()- 為什么?因?yàn)?Spy 默認(rèn)執(zhí)行真實(shí)方法,如果用
when(spy.method()),真實(shí)方法會(huì)立即執(zhí)行,可能導(dǎo)致空指針異常。
- 為什么?因?yàn)?Spy 默認(rèn)執(zhí)行真實(shí)方法,如果用
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
// 1. 必須初始化真實(shí)對(duì)象!不能寫(xiě) @Spy private UserService userService; (這樣會(huì)報(bào)空指針)
@Spy
private UserService userService = new UserService();
@Mock
private UserRepository userRepository;
@Test
void testSpyUsage() {
// --- Arrange ---
// 假設(shè) UserService 有個(gè)方法 calculateTax() 很復(fù)雜,我們想 Mock 它
// 注意語(yǔ)法:doReturn(...).when(spy).method()
doReturn(100.0).when(userService).calculateTax();
// --- Act ---
// 調(diào)用其他未 Mock 的方法,會(huì)執(zhí)行真實(shí)邏輯
// 調(diào)用 calculateTax,會(huì)返回 100.0
double tax = userService.calculateTax();
// --- Assert ---
assertEquals(100.0, tax);
// --- Verify ---
// 驗(yàn)證真實(shí)方法是否被調(diào)用
verify(userService, times(1)).calculateTax();
}
}2. Spring 集成測(cè)試(使用@SpyBean)
當(dāng)你使用 @SpringBootTest 時(shí),普通的 @Spy 無(wú)法替換 Spring 容器里的 Bean。這時(shí)要用 Spring Boot 提供的 @SpyBean。
作用:把 Spring 容器里原本的 Bean 替換成一個(gè) Spy 對(duì)象。
@SpringBootTest
class OrderServiceIntegrationTest {
@Autowired
private OrderService orderService; // 真實(shí)的 Service
// 1. @SpyBean:替換容器里的 UserService,但保留真實(shí)邏輯
@SpyBean
private UserService userService;
@Test
void testOrderWithSpyBean() {
// --- Arrange ---
// 攔截 getUserLevel 方法,返回 "VIP"
doReturn("VIP").when(userService).getUserLevel(anyLong());
// --- Act ---
// 調(diào)用 orderService,它會(huì)調(diào)用 userService.getUserLevel
// 此時(shí) getUserLevel 返回 "VIP",但 userService 的其他方法(如 saveUser)仍走真實(shí)數(shù)據(jù)庫(kù)邏輯(如果配置了的話)
orderService.createOrder(1L);
// --- Verify ---
// 驗(yàn)證 getUserLevel 確實(shí)被調(diào)用了
verify(userService).getUserLevel(1L);
}
}?? Mock vs Spy:怎么選?
為了讓你更清晰地做決定,我整理了這個(gè)對(duì)比表:
| 維度 | @Mock (完全模擬) | @Spy / @SpyBean (部分模擬) |
|---|---|---|
| 真實(shí)代碼執(zhí)行 | 絕不執(zhí)行 | 默認(rèn)執(zhí)行 (除非被攔截) |
| 初始化要求 | 不需要實(shí)例化 | 必須有真實(shí)實(shí)例 (new Object()) |
| 存根語(yǔ)法 | when(mock.method())... | doReturn(...).when(spy)... |
| 風(fēng)險(xiǎn) | 低(完全隔離) | 中(真實(shí)代碼可能拋異常或依賴數(shù)據(jù)庫(kù)) |
| 適用場(chǎng)景 | 依賴對(duì)象(Repository, Client) | 被測(cè)對(duì)象本身(想測(cè)部分邏輯)、遺留代碼 |
?? Spy vs InjectMocks
| 維度 | @Spy | @InjectMocks |
|---|---|---|
| 核心職責(zé) | 部分模擬。包裝一個(gè)真實(shí)對(duì)象,保留真實(shí)邏輯,但允許攔截特定方法。 | 依賴注入。創(chuàng)建被測(cè)對(duì)象,并自動(dòng)把 @Mock 或 @Spy 塞進(jìn)去。 |
| 代碼行為 | 默認(rèn)執(zhí)行真實(shí)代碼。 | 負(fù)責(zé)初始化對(duì)象(通過(guò)構(gòu)造函數(shù)或字段注入)。 |
| 語(yǔ)法陷阱 | 必須手動(dòng)初始化實(shí)例(= new UserService()),否則報(bào)錯(cuò)。 | 不需要手動(dòng)初始化,Mockito 會(huì)自動(dòng)幫你 new 出來(lái)。 |
| 常用搭配 | 用于被測(cè)對(duì)象本身(當(dāng)你不想 Mock 所有方法時(shí))。 | 用于被測(cè)對(duì)象(當(dāng)你想完全隔離,只測(cè)邏輯流轉(zhuǎn)時(shí))。 |
| 存根語(yǔ)法 | 必須用 doReturn(...).when(spy)... | (它本身不存根,它注入的對(duì)象如果是 Mock,則用 when...thenReturn) |
?? 避坑指南
- 初始化陷阱:使用 @Spy 時(shí),字段必須手動(dòng)初始化(如 = new UserService()),否則 Mockito 無(wú)法創(chuàng)建 Spy 對(duì)象,會(huì)報(bào) NullPointerException。
- Final 方法:和 Mock 一樣,Spy 也無(wú)法 Spy final 方法。調(diào)用 final 方法時(shí),永遠(yuǎn)執(zhí)行真實(shí)代碼,無(wú)法攔截。
- 自調(diào)用問(wèn)題:在 Spring 中,如果一個(gè) Bean 的方法 A 調(diào)用了同一個(gè)類的方法 B(this.methodB()),即使你 Spy 了方法 B,A 調(diào)用 B 時(shí)走的也是真實(shí)邏輯,Spy 的攔截可能失效(因?yàn)?Spring AOP 代理機(jī)制)。
總結(jié)建議:在單元測(cè)試中,優(yōu)先使用 @Mock,因?yàn)樗蓛簟⒏踩?。只有?dāng)你真的需要保留真實(shí)邏輯,或者為了省事不想 Mock 所有依賴時(shí),才使用 @Spy。
到此這篇關(guān)于Springboot結(jié)合Junit單元測(cè)試的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Springboot Junit單元測(cè)試內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java中xml和對(duì)象之間的互相轉(zhuǎn)換方法
在java開(kāi)發(fā)中我們經(jīng)常會(huì)遇到Xml與對(duì)象互相轉(zhuǎn)換的情況,這篇文章主要給大家介紹了關(guān)于java中xml和對(duì)象之間的互相轉(zhuǎn)換方法,文中給出了兩種解決方法,需要的朋友可以參考下2023-06-06
Springboot實(shí)現(xiàn)WebMvcConfigurer接口定制mvc配置詳解
這篇文章主要介紹了Springboot實(shí)現(xiàn)WebMvcConfigurer接口定制mvc配置詳解,spring?boot拋棄了傳統(tǒng)xml配置文件,通過(guò)配置類(標(biāo)注@Configuration的類,@Configuration配置類相當(dāng)于一個(gè)xml配置文件)以JavaBean形式進(jìn)行相關(guān)配置,需要的朋友可以參考下2023-09-09
Java?guava框架LoadingCache及CacheBuilder本地小容量緩存框架總結(jié)
Guava?Cache本地緩存框架主要是一種將本地?cái)?shù)據(jù)緩存到內(nèi)存中,但數(shù)據(jù)量并不能太大,否則將會(huì)占用過(guò)多的內(nèi)存,本文給大家介紹Java?guava框架?LoadingCache及CacheBuilder?本地小容量緩存框架總結(jié),感興趣的朋友一起看看吧2023-12-12
如何通過(guò)一張圖搞懂springBoot自動(dòng)注入原理
這篇文章主要給大家介紹了關(guān)于如何通過(guò)一張圖搞懂springBoot自動(dòng)注入原理的相關(guān)資料,文中通過(guò)圖文以及實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-02-02
解決hive啟動(dòng)時(shí)java.net.ConnectException:拒絕連接的問(wèn)題
Hadoop集群連接被拒,需檢查集群是否啟動(dòng)、關(guān)閉防火墻/SELinux、確認(rèn)安全模式退出,若問(wèn)題仍存,查看日志報(bào)錯(cuò)處理2025-08-08
Java利用過(guò)濾器實(shí)現(xiàn)完善登錄功能
這篇文章主要為大家詳細(xì)介紹了Java如何利用過(guò)濾器實(shí)現(xiàn)完善登錄功能,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Java有一定幫助,需要的可以參考一下2022-09-09

