Using Mockito ArgumentCaptor – 使用Mockito ArgumentCaptor

最后修改: 2020年 7月 30日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll cover a common use case of using Mockito ArgumentCaptor in our unit tests.

在本教程中,我们将介绍在单元测试中使用Mockito ArgumentCaptor的一个常见用例。

Alternatively, for other Mockito.verify use cases, see our Mockito Verify Cookbook.

另外,对于其他Mockito.verify使用案例,请参阅我们的Mockito Verify Cookbook

2. Using ArgumentCaptor

2.使用ArgumentCaptor

ArgumentCaptor allows us to capture an argument passed to a method to inspect it. This is especially useful when we can’t access the argument outside of the method we’d like to test.

ArgumentCaptor允许我们捕获传递给方法的参数来检查它。当我们无法在我们想要测试的方法之外访问该参数时,这一点特别有用。

For example, consider an EmailService class with a send method that we’d like to test:

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

    ...
}

In EmailService.send, notice how platform.deliver takes a new Email as an argument. As part of our test, we’d like to check that the format field of the new Email is set to Format.HTML. To do this, we need to capture and inspect the argument that is passed to platform.deliver.

EmailService.send中,注意platform.deliver如何接收一个新的Email作为一个参数。作为测试的一部分,我们想检查新Email的格式字段是否被设置为Format.HTML。要做到这一点,我们需要捕获并检查传递给 platform.deliver的参数。

Let’s see how we can use ArgumentCaptor to help us.

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

2.1. Set Up the Unit Test

2.1.设置单元测试

First, let’s create our unit test class:

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

@RunWith(MockitoJUnitRunner.class)
public class EmailServiceUnitTest {

    @Mock
    DeliveryPlatform platform;

    @InjectMocks
    EmailService emailService;
  
    ...
}

We’re using the @Mock annotation to mock DeliveryPlatform, which is automatically injected into our EmailService with the @InjectMocks annotation. Refer to our Mockito annotations article for further details.

我们使用@Mock注解来模拟DeliveryPlatform,它通过@InjectMocks注解被自动注入到我们的EmailService。请参考我们的Mockito注解文章以了解更多细节。

2.2. Add an ArgumentCaptor Field

2.2.添加一个ArgumentCaptor字段

Second, let’s add a new ArgumentCaptor field of type Email to store our captured argument:

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

@Captor
ArgumentCaptor<Email> emailCaptor;

2.3. Capture the Argument

2.3.捕捉论据

Third, let’s use Mockito.verify with the ArgumentCaptor to capture the Email:

第三,让我们使用Mockito.verifyArgumentCaptor来捕获Email

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

We can then get the captured value and store it as a new Email object:

然后我们可以获得捕获的值,并将其存储为一个新的Email对象。

Email emailCaptorValue = emailCaptor.getValue();

2.4. Inspect the Captured Value

2.4.检查捕获的价值

Finally, let’s see the whole test with an assert to inspect the captured Email object:

最后,让我们看看整个测试,用一个断言来检查捕获的Email对象。

@Test
public void whenDoesSupportHtml_expectHTMLEmailFormat() {
    String to = "info@baeldung.com";
    String subject = "Using ArgumentCaptor";
    String body = "Hey, let'use ArgumentCaptor";

    emailService.send(to, subject, body, true);

    Mockito.verify(platform).deliver(emailCaptor.capture());
    Email value = emailCaptor.getValue();
    assertThat(value.getFormat()).isEqualTo(Format.HTML);
}

3. Avoiding Stubbing

3.避开残枝败叶

Although we can use an ArgumentCaptor with stubbing, we should generally avoid doing so. To clarify, in Mockito, this generally means avoiding using an ArgumentCaptor with Mockito.when. With stubbing, we should use an ArgumentMatcher instead.

虽然我们可以在存根中使用ArgumentCaptor,但我们通常应该避免这样做。澄清一下,在Mockito中,这通常意味着避免在Mockito.when中使用ArgumentCaptor。对于存根,我们应该使用一个ArgumentMatcher来代替。

Let’s look at a couple of reasons why we should avoid stubbing.

让我们来看看为什么我们应该避免存根的几个原因。

3.1. Decreased Test Readability

3.1.测试可读性降低

First, consider a simple test:

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

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

assertTrue(emailService.authenticatedSuccessfully(credentials));

Here we use Mockito.eq(credentials) to specify when the mock should return an object.

这里我们使用Mockito.eq(credentials)来指定mock应该何时返回一个对象。

Next, consider the same test using an ArgumentCaptor instead:

接下来,考虑用一个ArgumentCaptor代替同样的测试。

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

assertTrue(emailService.authenticatedSuccessfully(credentials));
assertEquals(credentials, credentialsCaptor.getValue());

In contrast to the first test, notice how we have to perform an extra assert on the last line to do the same as Mockito.eq(credentials).

与第一个测试相比,注意到我们必须在最后一行执行一个额外的断言,以做到与Mockito.eq(credentials)相同。

Finally, notice how it isn’t immediately clear what credentialsCaptor.capture() refers to. This is because we have to create the captor outside the line we use it on, reducing readability.

最后,请注意,我们并不清楚credentialsCaptor.capture()所指的是什么。这是因为我们必须在我们使用它的行之外创建captor,从而降低了可读性。

3.2. Reduced Defect Localization

3.2.减少的缺陷定位

Another reason is that if emailService.authenticatedSuccessfully doesn’t call platform.authenticate, we’ll get an exception:

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

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

This is because our stubbed method hasn’t captured an argument. However, the real issue is not in our test itself but in the actual method we are testing.

这是因为我们的存根方法还没有捕捉到一个参数。然而,真正的问题不在我们的测试本身,而在我们正在测试的实际方法。

In other words, it misdirects us to an exception in the test, whereas the actual defect is in the method we are testing.

换句话说,它将我们误导到测试中的一个异常,而实际的缺陷是在我们测试的方法中。

4. Conclusion

4.总结

In this short article, we looked at a general use case of using ArgumentCaptor. We also looked at the reasons for avoiding using ArgumentCaptor with stubbing.

在这篇短文中,我们看了一个使用ArgumentCaptor的一般用例。我们还研究了避免使用ArgumentCaptor与存根的原因。

As usual, all of our code samples are available over on GitHub.

像往常一样,我们所有的代码样本都可以在GitHub上找到