Injecting @Mock and @Captor in JUnit 5 Method Parameters – 在 JUnit 5 方法参数中注入 @Mock 和 @Captor

最后修改: 2024年 2月 6日

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

1. Overview

1.概述

In this tutorial, we’ll see how to inject the @Mock and @Captor annotations in unit test method parameters.

在本教程中,我们将了解如何在单元测试方法参数中注入 @Mock@Captor 注释。

We can use @Mock in our unit tests to create mock objects. On the other hand, we can use @Captor to capture and store arguments passed to mocked methods for later assertions. The introduction of JUnit 5 made it very easy to inject parameters into test methods, making room for this new feature.

我们可以在单元测试中使用 @Mock 来创建模拟对象。另一方面,我们可以使用 @Captor 来捕获和存储传递给模拟方法的参数,以便日后进行断言。JUnit 5 的引入使将参数注入测试方法变得非常容易,从而为这一新功能提供了空间。

2. Example Setup

2.设置示例

For this feature to work, we need to use JUnit 5. The latest version of the library can be found in the Maven Central Repository. Let’s add the dependency to our pom.xml:

为使该功能正常工作,我们需要使用 JUnit 5。该库的最新版本可在 Maven Central Repository 中找到。让我们将依赖关系添加到 pom.xml 中:

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.10.1</version>
    <scope>test</scope>
</dependency>

Mockito is a testing framework which allows us to create dynamic mock objects. Mockito Core provides the fundamental features of the framework, offering an expressive API for creating and interacting with mock objects. Let’s use its latest version:

Mockito是一个允许我们创建动态模拟对象的测试框架。Mockito Core 提供了该框架的基本功能,并为创建模拟对象和与之交互提供了极具表现力的 API。让我们使用其最新版本

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>5.9.0</version>
    <scope>test</scope>
</dependency>

Lastly, we need to use the Mockito JUnit Jupiter extension, which is responsible for integrating Mockito with JUnit 5. Let’s also add this dependency to our pom.xml:

最后,我们需要使用 Mockito JUnit Jupiter 扩展,它负责将 Mockito 与 JUnit 5 集成。让我们将 此依赖关系添加到 pom.xml 中:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-junit-jupiter</artifactId>
    <version>5.9.0</version>
    <scope>test</scope>
</dependency>

3. Injecting @Mock Through Method Parameters

3.通过方法参数注入 @Mock

First, let’s attach the Mockito extension to our unit test class:

首先,让我们将 Mockito 扩展附加到单元测试类:

@ExtendWith(MockitoExtension.class)
class MethodParameterInjectionUnitTest {

    // ...

}

Registering the Mockito extension allows the Mockito framework to integrate with the JUnit 5 testing framework. Thus, we can now supply our mock object as a test parameter:

注册 Mockito 扩展允许 Mockito 框架与 JUnit 5 测试框架集成。因此,我们现在可以将我们的 mock 对象作为测试参数提供: <br

@Test
void whenMockInjectedViaArgumentParameters_thenSetupCorrectly(@Mock Function<String, String> mockFunction) {
    when(mockFunction.apply("bael")).thenReturn("dung");
    assertEquals("dung", mockFunction.apply("bael"));
}

In this example, our mock function returns the String “dung” when we pass “bael” as an input. The assertion demonstrates that the mock behaves as we expect.

在此示例中,当我们将 “bael” 作为输入时,我们的 mock 函数返回 String “dung”断言证明了 mock 的行为符合我们的预期。

Besides, constructors are a kind of method, so it’s also possible to inject @Mock as a parameter of the constructor of the test class:

此外,构造函数也是一种方法,因此也可以注入 @Mock 作为测试类构造函数的参数:

@ExtendWith(MockitoExtension.class)
class ConstructorInjectionUnitTest {
    
    Function<String, String> function;

    public ConstructorInjectionUnitTest(@Mock Function<String, String> functionr) {
        this.function = function;
    }
    
    @Test
    void whenInjectedViaArgumentParameters_thenSetupCorrectly() {
        when(function.apply("bael")).thenReturn("dung");
        assertEquals("dung", function.apply("bael"));
    }

}

On the whole, mock injection isn’t limited to basic unit tests. For instance, we can also inject mocks into other kinds of testable methods, like repeated tests or parameterized tests:

总的来说,mock 注入并不局限于基本的单元测试。例如,我们还可以将 mock 注入其他类型的可测试方法,如 repeated testsparameterized tests

@ParameterizedTest
@ValueSource(strings = {"", "bael", "dung"})
void whenInjectedInParameterizedTest_thenSetupCorrectly(String input, @Mock Function<String, String> mockFunction) {
    when(mockFunction.apply(input)).thenReturn("baeldung");
    assertEquals("baeldung", mockFunction.apply(input));
}

Lastly, let’s note that the order of the method parameters matters when we inject a mock into a parameterized test. The mockFunction injected mock must come after the input test parameter for the parameter resolver to do its job correctly.

最后,让我们注意一下,当我们将 mock 注入参数化测试时,方法参数的顺序很重要。注入的 mockFunction 必须位于 input 测试参数之后,这样参数解析器才能正常工作。

4. Injecting @Captor Through Method Parameters

4.通过方法参数注入 @Captor

ArgumentCaptors allow to check the values of objects we can’t access by other means in our tests. We can now inject @Captor through method parameters in a very similar way:

ArgumentCaptors 允许检查我们在测试中无法通过其他方式访问的对象的值。我们现在可以通过方法参数以非常类似的方式注入 @Captor:

@Test
void whenArgumentCaptorInjectedViaArgumentParameters_thenSetupCorrectly(@Mock Function<String, String> mockFunction, @Captor ArgumentCaptor<String> captor) {
    mockFunction.apply("baeldung");
    verify(mockFunction).apply(captor.capture());
    assertEquals("baeldung", captor.getValue());
}

In this example, we apply our mocked function to the String “baeldung”. Then, we use the ArgumentCaptor to extract the value passed to the function call. In the end, we verify that this value is correct.

在此示例中,我们将模拟函数应用到 String “baeldung” 中。然后,我们使用 ArgumentCaptor 提取传递给函数调用的值。最后,我们将验证该值是否正确。

All the remarks we made about mock injection are also valid for captors. In particular, let’s see an example of injection in an @RepeatedTest this time:

我们就 mock 注入所做的所有说明同样适用于捕获器。特别是,这次让我们看看在 @RepeatedTest 中注入的示例:

@RepeatedTest(2)
void whenInjectedInRepeatedTest_thenSetupCorrectly(@Mock Function<String, String> mockFunction, @Captor ArgumentCaptor<String> captor) {
    mockFunction.apply("baeldung");
    verify(mockFunction).apply(captor.capture());
    assertEquals("baeldung", captor.getValue());
}

5. Why Use Method Argument Injection?

5.为什么要使用方法参数注入?

We’ll now look at the advantages of this new feature. First, let’s recall how we used to declare our mocks before:

现在我们来看看这项新功能的优势。首先,让我们回顾一下以前我们是如何声明模拟的:

Mock<Function> mock = mock(Mock.class)

In this case, the compiler issues a warning because Mockito.mock() can’t create correctly the generic type of Function. Thanks to method parameter injection, we’re able to preserve the generic type signature, and the compiler stops complaining.

在这种情况下,编译器会发出警告,因为 Mockito.mock() 无法正确创建 Function 泛型。多亏了方法参数注入,我们才得以保留通用类型签名,编译器也不再抱怨了。

Another great advantage of using method injection is spotting dependencies. Before, we needed to inspect the test code to understand the interactions with other classes. With method parameter injection, the method signature shows how our system under test interacts with other components. Furthermore, the test code is shorter and more focused towards its goal.

使用方法注入的另一大优势是发现依赖关系。以前,我们需要检查测试代码以了解与其他类的交互。有了方法参数注入,方法签名将显示我们的被测系统是如何与其他组件交互的。

6. Conclusion

6.结论

In this article, we saw how to inject @Mock and @Captor via method arguments. The support for constructor and method dependency injection in JUnit 5 enabled this feature. To conclude, it’s recommended to use this new feature. It may sound only like a nice-to-have at first glance, but it can enhance our code quality and readability.

在本文中,我们了解了如何通过方法参数注入 @Mock@Captor 。JUnit 5 对构造函数和方法依赖注入的支持启用了这一功能。最后,建议使用这一新功能。乍听之下,它可能只是一个不错的功能,但它能提高我们的代码质量和可读性。

As always, the code for the examples is available over on GitHub.

与往常一样,这些示例的代码可在 GitHub 上获取。