前置条件:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.5.1</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>4.5.1</version>
<scope>test</scope>
</dependency>
一、模拟静态方法 mockStatic()
用途:模拟工具类的静态方法
依赖:Mockito 3.4.0+ 和 mockito-inline
示例场景:模拟 StringUtils 工具类
// 工具类
public class StringUtils {
public static boolean isEmpty(String str) {
return str == null || str.trim().isEmpty();
}
}
// 测试类
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
@ExtendWith(MockitoExtension.class)
class StaticMockTest {
@Test
void testMockStatic() {
// 1. 创建静态方法的模拟作用域
try (MockedStatic<StringUtils> mockedStatic = Mockito.mockStatic(StringUtils.class)) {
// 2. 设置模拟行为
mockedStatic.when(() -> StringUtils.isEmpty("hello"))
.thenReturn(false); // 模拟非空
mockedStatic.when(() -> StringUtils.isEmpty(""))
.thenReturn(true); // 模拟空
// 3. 验证模拟行为
assertFalse(StringUtils.isEmpty("hello"));
assertTrue(StringUtils.isEmpty(""));
// 4. 验证调用次数
mockedStatic.verify(() -> StringUtils.isEmpty("hello"));
mockedStatic.verify(() -> StringUtils.isEmpty(""), Mockito.times(1));
}
// 5. 作用域外恢复原始行为
assertTrue(StringUtils.isEmpty("")); // 调用真实方法
}
}
关键点:
- 使用 try-with-resources 确保模拟作用域安全关闭
- 作用域内静态方法被模拟,作用域外自动恢复
- 通过 verify 验证调用次数
二、通过反射模拟私有方法
用途:测试公共方法调用的私有逻辑
注意:优先重构代码,避免测试私有方法
示例场景:模拟 UserService 的私有方法
// 被测试类
public class UserService {
public String getUserRole(String userId) {
return isAdmin(userId) ? "ADMIN" : "USER"; // 调用私有方法
}
private boolean isAdmin(String userId) {
// 真实逻辑(如查数据库)
return "admin123".equals(userId);
}
}
// 测试类
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.lang.reflect.Method;
@ExtendWith(MockitoExtension.class)
class PrivateMethodTest {
@Test
void testMockPrivateMethod() throws Exception {
// 1. 创建 Spy 对象(部分模拟)
UserService spyService = Mockito.spy(new UserService());
// 2. 使用反射覆盖私有方法
Method isAdminMethod = UserService.class
.getDeclaredMethod("isAdmin", String.class);
isAdminMethod.setAccessible(true); // 突破私有访问限制
// 3. 修改私有方法行为
Mockito.doReturn(true)
.when(spyService)
.isAdmin("user456"); // 注意:需编译器支持方法名引用
// 4. 测试公共方法
assertEquals("ADMIN", spyService.getUserRole("user456")); // 模拟返回 ADMIN
assertEquals("USER", spyService.getUserRole("other")); // 原始逻辑
}
}
关键点:
- 通过 spy() 创建对象代理
- 反射设置私有方法可访问
- 使用 doReturn().when() 避免调用真实方法
三、使用 @Spy 模拟真实对象
用途:部分模拟对象,保留部分真实逻辑
示例场景:模拟 PaymentProcessor 的部分方法
// 被测试类
public class PaymentProcessor {
public String validateCard(String card) {
return isValid(card) ? "VALID" : "INVALID";
}
public boolean isValid(String card) {
// 复杂验证逻辑(如调用外部服务)
return card.length() == 16;
}
}
// 测试类
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class) // 启用 Mockito 注解
class SpyAnnotationTest {
@Spy // 创建部分模拟对象
PaymentProcessor paymentProcessor = new PaymentProcessor();
@Test
void testSpyAnnotation() {
// 1. 保留真实方法
assertTrue(paymentProcessor.isValid("1234567890123456"));
// 2. 模拟特定方法
doReturn(true).when(paymentProcessor).isValid("1111");
// 3. 测试依赖方法
assertEquals("VALID", paymentProcessor.validateCard("1111")); // 使用模拟
assertEquals("INVALID", paymentProcessor.validateCard("short")); // 使用真实
}
}
关键点:
- @Spy 需配合 @ExtendWith(MockitoExtension.class) 使用
- 默认调用真实方法,可对指定方法模拟
- 用 doReturn().when() 避免真实方法被调用
实践建议
- 避免通过反射模拟私有方法,可以通过调用外层的public方法,进行间接调用