在Java单元测试中,Mockito是模拟依赖的首选工具。本文将全面解析@Mock
、@Spy
、@Captor
和@InjectMocks
四大核心注解的使用场景与最佳实践,助你编写出高可维护性的测试代码~
一、环境准备:启用Mockito注解
在测试类中启用注解支持的三种方式:
- JUnit Runner(推荐):适合大多数测试场景
@RunWith(MockitoJUnitRunner.class)public class ServiceTest {// 测试方法
}
- 手动初始化:灵活控制初始化时机
@Beforepublic void initMocks() {
MockitoAnnotations.openMocks(this);
}
- JUnit Rule:适合需要与其他测试规则配合使用
@Rulepublic MockitoRule mockitoRule = MockitoJUnit.rule();
二、@Mock:创建模拟对象
适用场景:需要完全隔离依赖对象时
public class UserServiceTest {@Mockprivate UserRepository userRepo;
@Testpublic void testFindUser() {// 配置模拟行为when(userRepo.findById(1001L))
.thenReturn(new User("张三"));
User result = userService.findUser(1001L);
assertThat(result.getName()).isEqualTo("张三");
}
}
关键功能: • 模拟方法返回值:when(...).thenReturn(...)
• 模拟异常抛出:when(...).thenThrow(...)
• 验证调用次数:verify(mock, times(2)).method()
三、@Spy:部分真实的对象
适用场景:需要保留对象主要功能,仅修改特定方法时
public class OrderProcessorTest {@Spyprivate InventoryService inventory = new InventoryService();
@Testpublic void testStockCheck() {// 覆盖库存检查方法
doReturn(50).when(inventory).getStock("ITEM_001");
boolean available = orderProcessor.checkAvailability("ITEM_001", 30);
assertTrue(available);
}
}
注意事项:
- 优先使用
doReturn().when()
语法 - 避免在
@Spy
对象上直接调用when()
,可能触发真实方法 - 适合需要保留部分业务逻辑的测试场景
四、@Captor:参数捕获器
适用场景:需要验证方法调用时传入的具体参数
public class AuditServiceTest {@Mockprivate AuditLogger logger;
@Captorprivate ArgumentCaptor<LogEntry> logCaptor;
@Testpublic void testLoginAudit() {
userService.login("user1", "password123");
verify(logger).record(logCaptor.capture());LogEntry entry = logCaptor.getValue();
assertThat(entry.getAction()).isEqualTo("LOGIN");
assertThat(entry.getUsername()).isEqualTo("user1");
}
}
典型用途:
• 验证复杂对象的属性值
• 检查集合类型参数的内容
• 捕获多次调用的参数序列
五、@InjectMocks:依赖自动注入
适用场景:测试包含多层依赖的复杂服务
public class PaymentServiceTest {@Mockprivate PaymentGateway gateway;
@Mockprivate TransactionRepo txRepo;
@InjectMocksprivate PaymentService paymentService;
@Testpublic void testSuccessfulPayment() {when(gateway.process(any()))
.thenReturn(new PaymentResult(true, "SUCCESS"));
PaymentResult result = paymentService.execute(new PaymentRequest(100.0, "USD"));
assertTrue(result.isSuccess());
verify(txRepo).save(any());
}
}
注入规则:
- 按类型匹配构造函数参数
- 支持字段注入(需setter方法)
- 优先注入
@Mock
对象,未找到时尝试实例化真实对象
六、验证规范
验证类型 | 使用场景 |
---|---|
verify(mock) | 基本调用验证 |
times(n) | 验证精确调用次数 |
atLeastOnce() | 至少一次调用 |
never() | 确保从未调用 |
七、总结
通过合理运用Mockito的四大核心注解,我们可以构建出结构清晰、运行稳定的单元测试体系。关键要平衡测试的隔离性与真实性,既避免过度模拟导致测试失真,也要防止因依赖外部服务造成的测试脆弱性~
最后安利几个小tips:
- 注意保持测试单一职责:每个测试方法验证一个逻辑分支
- 使用
@Before
初始化通用配置
@Before
public void setup() {
when(configService.getTimeout()).thenReturn(30);
}
- 用静态导入让代码更清爽
import static org.mockito.Mockito.*;
// 这样就能直接写when()、verify()啦
下次写单元测试时,再来看看这篇文章,保证你的测试代码既简洁又专业(^_^)v~~