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上找到。


原始标题:Fix Ambiguous Method Call Error in Mockito | Baeldung