1. 概述
本文将介绍 Mockito 中 ArgumentCaptor
的一种常见用法 —— 在单元测试中捕获方法参数,用于后续断言和验证。
如果你还想了解其他 Mockito.verify
的使用方式,可以参考我们的 Mockito Verify 实战指南。
2. 使用 ArgumentCaptor
ArgumentCaptor
允许我们在测试过程中捕获传递给某个方法的参数对象,从而进行进一步的检查。✅ 这个功能特别适合在无法直接访问该参数的情况下使用,比如构造方法内部创建的对象。
2.1 示例场景:EmailService
假设我们有一个 EmailService
类,其中包含一个 send
方法:
public class EmailService {
private DeliveryPlatform platform;
public EmailService(DeliveryPlatform platform) {
this.platform = platform;
}
public void send(String to, String subject, String body, boolean html) {
Format format = Format.TEXT_ONLY;
if (html) {
format = Format.HTML;
}
Email email = new Email(to, subject, body);
email.setFormat(format);
platform.deliver(email);
}
...
}
在 send
方法内部,会根据是否支持 HTML 创建一个 Email
对象,并调用 platform.deliver(email)
。如果我们想验证这个 email
对象中的 format
是否正确设置为 Format.HTML
,就需要用到 ArgumentCaptor
来捕获这个参数。
2.2 编写单元测试类
首先,我们创建一个测试类,并注入相关依赖:
@ExtendWith(MockitoExtension.class)
class EmailServiceUnitTest {
@Mock
DeliveryPlatform platform;
@InjectMocks
EmailService emailService;
...
}
这里我们使用了 @Mock
注解来模拟 DeliveryPlatform
,并通过 @InjectMocks
将其自动注入到 EmailService
实例中。
2.3 定义 ArgumentCaptor 字段
接着,定义一个类型为 Email
的 ArgumentCaptor
字段,用来存储被捕获的参数:
@Captor
ArgumentCaptor<Email> emailCaptor;
2.4 捕获并验证参数
然后通过 verify()
配合 ArgumentCaptor
捕获传入 deliver
方法的 Email
参数:
verify(platform).deliver(emailCaptor.capture());
之后可以通过 getValue()
获取捕获到的对象:
Email emailCaptorValue = emailCaptor.getValue();
2.5 完整测试示例
最后来看完整的测试方法,验证 HTML 格式是否被正确设置:
@Test
void whenDoesSupportHtml_expectHTMLEmailFormat() {
String to = "[email protected]";
String subject = "Using ArgumentCaptor";
String body = "Hey, let'use ArgumentCaptor";
emailService.send(to, subject, body, true);
verify(platform).deliver(emailCaptor.capture());
Email value = emailCaptor.getValue();
assertThat(value.getFormat()).isEqualTo(Format.HTML);
}
3. 避免与 Stubbing 混用
虽然我们可以把 ArgumentCaptor
用于 stubbing(即配合 Mockito.when()
使用),但 ❌ 通常不建议这么做。
3.1 降低可读性
来看个例子对比一下。
✅ 正常使用 eq()
匹配器的方式:
Credentials credentials = new Credentials("baeldung", "correct_password", "correct_key");
when(platform.authenticate(eq(credentials))).thenReturn(AuthenticationStatus.AUTHENTICATED);
assertTrue(emailService.authenticatedSuccessfully(credentials));
❌ 而如果换成 ArgumentCaptor
的写法:
Credentials credentials = new Credentials("baeldung", "correct_password", "correct_key");
when(platform.authenticate(credentialsCaptor.capture())).thenReturn(AuthenticationStatus.AUTHENTICATED);
assertTrue(emailService.authenticatedSuccessfully(credentials));
assertEquals(credentials, credentialsCaptor.getValue());
可以看到,为了达到相同的效果,我们必须额外添加一行断言,这反而让代码变得啰嗦,而且 credentialsCaptor.capture()
的意图不够直观。
⚠️ 因为 captor 必须在 when(...)
之外声明,导致逻辑分散,影响阅读体验。
3.2 缺陷定位不准
另一个问题是:如果 emailService.authenticatedSuccessfully()
并没有调用 platform.authenticate()
,那么就会抛出异常:
org.mockito.exceptions.base.MockitoException:
No argument value was captured!
此时报错出现在测试代码中,但实际上真正的问题在于被测方法本身没有执行预期的行为。这就容易误导开发者去调试测试代码,而不是业务逻辑。
✅ 所以说,使用 captor 做 stubbing 会掩盖真正的缺陷位置,不利于问题排查。
4. 总结
在这篇文章中,我们介绍了如何使用 ArgumentCaptor
捕获方法参数并进行验证。同时也说明了为何应避免将其用于 stubbing 场景。
一如既往,所有示例代码都可以在 GitHub 项目 中找到。