参数捕获与分析
参数捕获是 Mockito 提供的核心功能之一,允许开发者捕获方法调用时传递的实际参数,并进行详细验证。通过 ArgumentCaptor
,可以深入分析参数内容,确保交互逻辑符合预期,尤其适用于验证复杂对象或多次调用的场景。
1. ArgumentCaptor 基础用法
1.1 创建参数捕获器
手动创建:
// 创建捕获器,指定参数类型
ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
注解驱动(推荐):
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Captor // 自动初始化
private ArgumentCaptor<User> userCaptor;
}
1.2 捕获并验证参数
@Test
void createUser_ShouldPassCorrectUserToDao() {
UserService userService = new UserService(mockUserDao);
userService.createUser("alice", 25);
// 捕获 save() 方法的参数
verify(mockUserDao).save(userCaptor.capture());
User capturedUser = userCaptor.getValue();
assertEquals("alice", capturedUser.getName());
assertEquals(25, capturedUser.getAge());
}
2. 多参数与多次调用处理
2.1 捕获多个参数
若方法有多个参数,需为每个参数创建独立的捕获器:
@Captor
private ArgumentCaptor<String> usernameCaptor;
@Captor
private ArgumentCaptor<Integer> ageCaptor;
@Test
void updateUser_ShouldCaptureMultipleArgs() {
userService.updateUser("alice", 30);
verify(mockUserDao).update(usernameCaptor.capture(), ageCaptor.capture());
assertEquals("alice", usernameCaptor.getValue());
assertEquals(30, ageCaptor.getValue());
}
2.2 捕获多次调用的参数
当方法被多次调用时,可获取所有历史参数:
@Test
void batchCreate_ShouldCaptureAllUsers() {
userService.batchCreate(Arrays.asList("alice", "bob"));
verify(mockUserDao, times(2)).save(userCaptor.capture());
List<User> capturedUsers = userCaptor.getAllValues();
assertThat(capturedUsers)
.extracting(User::getName)
.containsExactly("alice", "bob");
}
3. 复杂对象验证技巧
3.1 验证嵌套对象属性
结合 AssertJ 断言库,精准验证复杂对象的内部状态:
@Test
void placeOrder_ShouldCaptureOrderWithItems() {
Order order = new Order();
order.addItem(new Item("Book", 2));
orderService.placeOrder(order);
verify(mockOrderDao).save(orderCaptor.capture());
Order capturedOrder = orderCaptor.getValue();
assertThat(capturedOrder.getItems())
.hasSize(1)
.first()
.hasFieldOrPropertyWithValue("name", "Book")
.hasFieldOrPropertyWithValue("quantity", 2);
}
3.2 动态条件匹配
使用 Lambda 表达式或自定义匹配器实现灵活验证:
@Test
void sendNotification_ShouldCaptureValidEmail() {
notificationService.sendWelcomeEmail("user@test.com");
verify(mockEmailClient).send(emailCaptor.capture());
EmailRequest email = emailCaptor.getValue();
assertThat(email)
.matches(e -> e.getTo().equals("user@test.com")
&& e.getSubject().contains("Welcome"));
}
4. 异步场景参数捕获
在异步逻辑中,需结合等待机制确保参数被正确捕获:
4.1 使用 Awaitility 等待异步调用
@Test
void asyncProcess_ShouldCaptureCallbackParams() {
asyncProcessor.process("data", mockCallback);
// 等待异步操作完成
await().atMost(1, TimeUnit.SECONDS)
.untilAsserted(() -> verify(mockCallback).onComplete(resultCaptor.capture()));
assertEquals("PROCESSED: data", resultCaptor.getValue());
}
5. 常见陷阱与解决方案
问题 | 解决方案 |
---|---|
捕获器未初始化 | 使用 @Captor 注解或手动调用 ArgumentCaptor.forClass() 。 |
捕获参数后未验证 | 始终对捕获的参数执行断言,避免“假通过”。 |
多次调用参数混淆 | 使用 getAllValues() 区分不同调用,或结合 times() 验证具体调用次数。 |
泛型类型擦除问题 | 为泛型类指定具体类型:new ArgumentCaptor<GenericType<String>>() {} 。 |
6. 最佳实践
- 精准捕获:仅捕获需要深度验证的参数,避免过度使用。
- 断言优先:优先验证参数内容,而非仅检查方法是否被调用。
- 结合匹配器:简单验证使用
any()
,复杂验证使用ArgumentCaptor
。 - 保持测试独立:在
@BeforeEach
中重置捕获器,避免跨测试污染。
总结
参数捕获与分析是单元测试中验证复杂交互逻辑的关键技术。通过合理使用 ArgumentCaptor
,开发者可以深入方法调用的细节,确保数据传递的准确性和业务逻辑的可靠性。结合断言库和异步等待机制,能够覆盖从简单到复杂的所有测试场景。