1. 概述

在这个教程中,我们将探讨在单元测试中使用Mockito的ArgumentCaptor的常见用例。

对于其他Mockito验证用例,请参阅我们的Mockito验证食谱

2. 使用 ArgumentCaptor

ArgumentCaptor 允许我们捕获传递给方法的参数以进行检查。当我们在测试中无法访问方法外部的参数时,这特别有用。

例如,考虑一个名为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);
    }

    ...
}

EmailService.send中,注意platform.deliver接受一个新的Email作为参数。在测试中,我们想检查新Emailformat字段是否设置为Format.HTML。为此,我们需要捕获并检查传递给platform.deliver的参数。

让我们看看如何使用ArgumentCaptor来帮助我们。

2.1. 设置单元测试

首先,创建我们的单元测试类:

@ExtendWith(MockitoExtension.class)
class EmailServiceUnitTest {

    @Mock
    DeliveryPlatform platform;

    @InjectMocks
    EmailService emailService;
  
    ...
}

我们使用@Mock注解来模拟DeliveryPlatform,它将自动由@InjectMocks注解注入到EmailService中。有关更多详细信息,请参考我们的Mockito注解文章。

2.2. 添加 ArgumentCaptor 字段

其次,添加一个新的Email类型的ArgumentCaptor字段来存储捕获的参数:

@Captor
ArgumentCaptor<Email> emailCaptor;

2.3. 捕获参数

第三,使用verify()ArgumentCaptor捕获Email

verify(platform).deliver(emailCaptor.capture());

然后我们可以获取捕获的值,并将其存储为新的Email对象:

Email emailCaptorValue = emailCaptor.getValue();

2.4. 检查捕获的值

最后,查看整个测试,带有断言来检查捕获的Email对象:

@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. 避免使用模拟

虽然我们可以在模拟时使用ArgumentCaptor,但通常应避免这样做。具体来说,在Mockito中,这意味着通常避免使用ArgumentCaptorMockito.when一起使用。使用模拟时,我们应该使用ArgumentMatcher

让我们来看看为什么应该避免使用模拟的一些原因。

3.1. 减少测试可读性

首先,考虑一个简单的测试:

Credentials credentials = new Credentials("baeldung", "correct_password", "correct_key");
when(platform.authenticate(eq(credentials))).thenReturn(AuthenticationStatus.AUTHENTICATED);

assertTrue(emailService.authenticatedSuccessfully(credentials));

这里我们使用eq(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());

与第一个测试相比,注意我们必须在最后一行执行额外的断言,才能达到eq(credentials)的效果。

最后,注意我们不能立即理解credentialsCaptor.capture()指的是什么。这是因为我们必须在使用它的行之外创建捕获器,这降低了可读性。

3.2. 减少缺陷定位

另一个原因是,如果emailService.authenticatedSuccessfully没有调用platform.authenticate,我们会收到一个异常:

org.mockito.exceptions.base.MockitoException: 
No argument value was captured!

这是因为我们模拟的方法没有捕获任何参数。然而,实际问题并不在于测试本身,而是在我们正在测试的实际方法。

换句话说,它会误导我们查找测试中的异常,而实际的缺陷在于我们正在测试的方法。

4. 总结

在这篇简短的文章中,我们探讨了使用ArgumentCaptor的一般用例。我们还讨论了避免在模拟时使用ArgumentCaptor的原因。

如往常一样,所有代码示例可在GitHub上找到。


« 上一篇: SDKMAN指南!
» 下一篇: 删除Docker镜像