Mockito 进阶:参数捕获器与间谍
使用 ArgumentCaptor 捕获方法参数,并使用 Mockito.spy 对真实对象进行部分模拟
Mockito 进阶:参数捕获器与间谍 是 CoddyKit 上的免费 Java Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Java Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Java Academy 课程共包含 4 节课。
ArgumentCaptor:捕获方法参数
ArgumentCaptor 会捕获传递给模拟方法的参数,以便您对这些参数进行断言。当参数是在被测试系统内部构建、无法直接访问时,这一功能非常有用。
创建并使用 ArgumentCaptor
为参数类型创建捕获器,在 verify() 中将 captor.capture() 作为参数匹配器传入,然后调用 captor.getValue() 获取捕获的参数。
@Test
void sends_correct_email() {
ArgumentCaptor<EmailMessage> captor = ArgumentCaptor.forClass(EmailMessage.class);
userService.register(new CreateUserRequest("alice@example.com"));
verify(emailService).send(captor.capture());
EmailMessage msg = captor.getValue();
assertEquals("alice@example.com", msg.getTo());
assertTrue(msg.getSubject().contains("Welcome"));
}@Captor 注解
将 @Captor 与 @ExtendWith(MockitoExtension.class) 一起使用,可以避免冗长的 ArgumentCaptor.forClass() 调用。
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock EmailService emailService;
@Captor ArgumentCaptor<EmailMessage> emailCaptor;
@Test
void captures_email() {
// ...
verify(emailService).send(emailCaptor.capture());
assertEquals("Welcome!", emailCaptor.getValue().getSubject());
}
}捕获多次调用
当方法被调用多次时,使用 captor.getAllValues()。它会按调用顺序返回所有捕获值组成的列表。
verify(emailService, times(3)).send(emailCaptor.capture());
List<EmailMessage> messages = emailCaptor.getAllValues();
assertEquals(3, messages.size());
assertEquals("Confirmation", messages.get(0).getSubject());Mockito 间谍:部分模拟
Mockito.spy() 会包装一个真实对象。除非您明确对方法进行桩设,否则所有方法都会正常运行。当您需要测试一个包含部分难以测试的依赖、但整体运行正常的类时,可以使用它。
List<String> realList = new ArrayList<>();
List<String> spy = Mockito.spy(realList);
spy.add("hello"); // calls real ArrayList.add()
System.out.println(spy.size()); // 1 — real method called
doReturn(42).when(spy).size(); // stub just size()
System.out.println(spy.size()); // 42 — stubbed@Spy 注解
将 @Spy 与 @ExtendWith(MockitoExtension.class) 一起使用,可以创建间谍对象。该字段必须已初始化(可以直接初始化,也可以通过 @Spy UserService svc = new UserService(repo); 初始化)。
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Spy
OrderService orderService = new OrderService(mockRepo, mockEmail);
}使用 @SpyBean 监视 Spring Bean
在 Spring Boot 测试中,@SpyBean 会将现有 Spring Bean 包装为 Mockito 间谍对象——除非进行桩设,否则它会执行真实行为。它通常与 @SpringBootTest 配合使用。
@SpringBootTest
class IntegrationTest {
@SpyBean EmailService emailService;
@Test
void order_sends_email() {
orderService.placeOrder(cart);
verify(emailService).sendConfirmation(any());
}
}间谍对象中的 doReturn 与 when().thenReturn()
对于间谍对象,请使用 doReturn(value).when(spy).method(),而不是 when(spy.method()).thenReturn(value)。when(spy.method()) 形式实际上会调用真实方法,可能引发副作用。
// Correct for spies:
doReturn(List.of()).when(spy).findAll();
// WRONG for spies — calls real findAll() first:
when(spy.findAll()).thenReturn(List.of()); // real method called!验证没有交互
使用 verifyNoInteractions(mock) 断言模拟对象从未被调用;在所有预期调用完成后使用 verifyNoMoreInteractions(mock),以捕获意外调用。
verifyNoInteractions(emailService); // never called
verify(repo).save(any());
verifyNoMoreInteractions(repo); // save() was the only interactionInOrder 验证
使用 InOrder 断言模拟方法是否按照指定顺序被调用。
InOrder inOrder = inOrder(repo, emailService);
inOrder.verify(repo).save(any());
inOrder.verify(emailService).send(any()); // save happened BEFORE send参数匹配器
将捕获器与匹配器结合使用:any()、eq()、anyString()、argThat(predicate)。如果有一个参数使用匹配器,所有参数都必须使用匹配器。
verify(repo).save(argThat(user ->
user.getEmail().endsWith("@example.com") &&
user.getStatus() == UserStatus.ACTIVE));快速检查
为间谍对象设置桩时,为什么使用 doReturn 而不是 when().thenReturn()?
回顾
ArgumentCaptor 会捕获参数,以便在调用完成后进行断言。间谍对象会包装真实对象,除非进行桩设,否则会将调用委托给真实对象。对间谍对象使用 doReturn。在 Spring 集成测试中使用 @SpyBean。使用 InOrder 验证调用顺序。
常见问题解答
「Mockito 进阶:参数捕获器与间谍」课时是免费的吗?
是的 — 「Mockito 进阶:参数捕获器与间谍」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Java Academy 课程的其余内容,请升级到 CoddyKit PRO。 Java Academy 课程共包含 4 节课。
「Mockito 进阶:参数捕获器与间谍」这节课中我会学到什么?
使用 ArgumentCaptor 捕获方法参数,并使用 Mockito.spy 对真实对象进行部分模拟 你通过在浏览器中直接运行的动手代码来练习 Java Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Java Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Java Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「Mockito 进阶:参数捕获器与间谍」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Java Academy 课中编写并运行代码吗?
能。每节 Java Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 使用 @CsvSource 和 @MethodSource 编写参数化测试
- Mockito 进阶:参数捕获器与间谍
- Spring Boot 测试切片:@WebMvcTest 与 @DataJpaTest
- Testcontainers:真实数据库集成测试