1. 概述
在这个教程中,我们将学习如何在单元测试方法参数中注入[@Mock]()
和[@Captor]()
注解。在单元测试中,我们可以使用[@Mock]()
创建模拟对象,而[@Captor]()
则用于捕获并存储传给模拟方法的参数,以便后续断言。随着JUnit 5的引入,将参数注入测试方法变得非常容易,这为这个新功能提供了可能。
2. 示例设置
为了使用这个特性,我们需要使用JUnit 5。库的最新版本可以在Maven中央仓库找到。让我们在pom.xml
中添加依赖:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
Mockito是一个测试框架,它允许我们创建动态模拟对象。Mockito核心提供了框架的基本功能,提供了一种表达式的API来创建和与模拟对象交互。让我们使用其最新版本:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.11.0</version>
<scope>test</scope>
</dependency>
最后,我们需要使用Mockito的JUnit Jupiter扩展,它负责将Mockito与JUnit 5集成。让我们也在pom.xml
中添加这个依赖:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.12.0</version>
<scope>test</scope>
</dependency>
3. 通过方法参数注入[@Mock]()
首先,让我们将Mockito扩展附加到我们的单元测试类上:
@ExtendWith(MockitoExtension.class)
class MethodParameterInjectionUnitTest {
// ...
}
注册Mockito扩展允许Mockito框架与JUnit 5测试框架集成。因此,我们现在可以将模拟对象作为测试参数提供:
@Test
void whenMockInjectedViaArgumentParameters_thenSetupCorrectly(@Mock Function<String, String> mockFunction) {
when(mockFunction.apply("bael")).thenReturn("dung");
assertEquals("dung", mockFunction.apply("bael"));
}
在这个例子中,我们的模拟函数在接收到输入“bael”时返回字符串“dung”。断言演示了模拟对象的行为符合我们的预期。
此外,构造函数也是一种方法,所以也可以将[@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"));
}
}
总的来说,模拟对象的注入并不局限于基本的单元测试。例如,我们还可以将模拟对象注入其他可测试的方法,如重复测试或参数化测试:
@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));
}
最后,需要注意的是,在参数化测试中注入模拟对象时,方法参数的顺序很重要。为了正确执行参数解析器,模拟函数注入的模拟对象必须位于输入测试参数之后。
4. 通过方法参数注入[@Captor]()
ArgumentCaptor允许我们在测试中检查无法通过其他方式访问的对象的值。现在,我们可以以非常相似的方式通过方法参数注入[@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());
}
在这个示例中,我们应用模拟函数到字符串“baeldung”。然后,我们使用ArgumentCaptor提取函数调用中的值。最后,我们验证这个值是正确的。
关于模拟对象注入的所有注意事项也适用于截获器。特别是,让我们看看在一个[@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. 为什么使用方法参数注入?
现在,我们将探讨这个新特性的优点。首先,让我们回顾一下在使用方法参数注入之前是如何声明模拟对象的:
Mock<Function> mock = mock(Mock.class)
在这种情况下,编译器会发出警告,因为Mockito.mock()
无法正确创建Function
的泛型类型。借助方法参数注入,我们能够保留泛型类型的签名,从而消除编译器的抱怨。
使用方法注入的另一个好处是识别依赖关系。以前,我们需要检查测试代码来理解与其他类的交互。有了方法参数注入,方法签名显示了我们的被测系统如何与其他组件交互。此外,测试代码更短,更专注于目标。
6. 总结
在这篇文章中,我们了解了如何通过方法参数注入[@Mock]()
和[@Captor]()
。JUnit 5对构造函数和方法依赖注入的支持使得这个功能成为可能。总的来说,推荐使用这个新特性。乍一看,这可能只是一个锦上添花的功能,但它可以提高我们的代码质量和可读性。
如往常一样,示例代码可在GitHub上找到。