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 字段

接着,定义一个类型为 EmailArgumentCaptor 字段,用来存储被捕获的参数:

@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 项目 中找到。


原始标题:Using Mockito ArgumentCaptor