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
作为参数。在测试中,我们想检查新Email
的format
字段是否设置为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中,这意味着通常避免使用ArgumentCaptor
与Mockito.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上找到。