1. 概述
在本教程中,我们将探讨如何在使用Mockito框架时解决方法调用的歧义问题。在Java中,方法重载允许类定义具有相同名称但不同参数的方法。当编译器无法根据提供的参数明确决定调用哪个具体方法时,就会出现歧义。
2. 引入Mockito的ArgumentMatchers
Mockito是一个用于单元测试Java应用的模拟框架。库的最新版本可以在Maven中央仓库找到。让我们在pom.xml
中添加依赖:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.11.0</version>
<scope>test</scope>
</dependency>
ArgumentMatchers
是Mockito框架的一部分:通过它们,我们可以指定模拟方法的行为,当传入的参数满足特定条件时。
3. 重载方法的定义
首先,我们定义一个接受Integer
参数并始终返回1
的方法:
Integer myMethod(Integer i) {
return 1;
}
为了演示,我们希望重载方法使用自定义类型。因此,我们先定义一个示例类:
class MyOwnType {}
现在,我们可以添加一个接受MyOwnType
对象作为参数且始终返回baeldung
的方法:
String myMethod(MyOwnType myOwnType) {
return "baeldung";
}
直观地说,如果我们传递一个null
参数给myMethod()
,编译器不知道该使用哪个版本。而且,我们注意到方法的返回类型对这个问题没有影响。
4. 使用isNull()
引发的歧义调用
让我们尝试性地使用基础的isNull()
ArgumentMatcher
来模拟带有null
参数的myMethod()
调用:
@Test
void givenMockedMyMethod_whenMyMethod_ThenMockedResult(@Mock MyClass myClass) {
when(myClass.myMethod(isNull())).thenReturn(1);
}
由于我们在测试方法参数中注入了定义myMethod()
的类的模拟对象(即MyClass
),我们顺利地实现了通过方法参数注入模拟对象。此外,目前我们的测试还没有添加任何断言。运行这段代码:
java.lang.Error: Unresolved compilation problem:
The method myMethod(Integer) is ambiguous for the type MyClass
可以看到,由于编译器无法决定使用哪个版本的myMethod()
,因此抛出了错误。请注意,编译器的决策仅基于方法参数。尽管我们在指令中写了thenReturn(1)
,但我们不能期望编译器在此过程中使用这部分内容。
为了解决这个问题,我们需要使用重载的isNull()
ArgumentMatcher
,它接受一个类作为参数。例如,要告诉编译器使用接受Integer
参数的那个版本,可以写成:
@Test
void givenMockedMyMethod_whenMyMethod_ThenMockedResult(@Mock MyClass myClass) {
when(myClass.myMethod(isNull(Integer.class))).thenReturn(1);
assertEquals(1, myClass.myMethod((Integer) null));
}
我们添加了一个断言来完成测试,现在它可以成功运行。同样,我们也可以修改测试以使用其他版本的方法:
@Test
void givenCorrectlyMockedNullMatcher_whenMyMethod_ThenMockedResult(@Mock MyClass myClass) {
when(myClass.myMethod(isNull(MyOwnType.class))).thenReturn("baeldung");
assertEquals("baeldung", myClass.myMethod((MyOwnType) null));
}
最后,要注意在断言中也需要提供myMethod()
的参数类型,否则也会因为同样的原因抛出错误!
5. 使用any()
引发的歧义调用
类似地,我们可以尝试模拟接受任意参数的myMethod()
调用,使用any()
ArgumentMatcher
:
@Test
void givenMockedMyMethod_whenMyMethod_ThenMockedResult(@Mock MyClass myClass) {
when(myClass.myMethod(any())).thenReturn(1);
}
再次运行这段代码,结果仍然是方法调用的歧义错误。之前的评论在这里仍然适用。特别是,编译器甚至不会查看thenReturn()
方法的参数就失败了。
解决方案也类似:我们需要使用明确指定预期参数类型的any()
ArgumentMatcher
版本:
@Test
void givenMockedMyMethod_whenMyMethod_ThenMockedResult(@Mock MyClass myClass) {
when(myClass.myMethod(anyInt())).thenReturn(1);
assertEquals(1, myClass.myMethod(2));
}
大多数基本Java类型已经为Mockito提供了相应的匹配方法。在我们这个例子中,anyInt()
会接受任何Integer
参数。另一方面,myMethod()
的另一个版本接受MyOwnType
类型的参数。因此,我们需要使用接受对象类型作为参数的any()
ArgumentMatcher
的重载版本:
@Test
void givenCorrectlyMockedNullMatcher_whenMyMethod_ThenMockedResult(@Mock MyClass myClass) {
when(myClass.myMethod(any(MyOwnType.class))).thenReturn("baeldung");
assertEquals("baeldung", myClass.myMethod((MyOwnType) null));
}
现在,我们的测试正常工作:我们成功消除了方法调用的歧义错误!
6. 总结
在这篇文章中,我们理解了在Mockito框架中遇到方法调用歧义的原因,并展示了如何解决这个问题。在实际情况下,这种问题通常会在有许多参数的重载方法中出现,我们可能会选择使用不太严格的isNull()
或any()
ArgumentMatcher
,因为某些参数的值与测试无关。在简单情况下,现代IDE通常能在我们运行测试之前指出问题。
如往常一样,代码可在GitHub上找到。