修复Mockito中的模糊方法调用错误

2025/03/15

1. 概述

在本教程中,我们将了解如何避免在Mockito框架的特定上下文中出现歧义的方法调用。

在Java中,方法重载允许一个类拥有多个名称相同但参数不同的方法。当编译器无法根据提供的参数确定要调用的具体方法时,就会发生模糊的方法调用。

2. 介绍Mockito的ArgumentMatchers

Mockito是一个用于单元测试Java应用程序的Mock框架,可以在Maven Central中找到该库的最新版本,让我们将依赖项添加到pom.xml中:

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

ArgumentMatchers是Mockito框架的一部分:借助它们,我们可以指定当参数匹配给定条件时Mock方法的行为

3. 重载方法定义

首先,让我们定义一个以Integer作为参数并始终返回1作为结果的方法:

Integer myMethod(Integer i) {
    return 1;
}

为了演示,我们希望重载方法使用自定义类型。因此,我们定义这个虚拟类:

class MyOwnType {}

我们现在可以添加一个重载的myMethod(),它接收MyOwnType对象作为参数并始终返回taketoday作为结果:

String myMethod(MyOwnType myOwnType) {
    return "taketoday";
}

直观地讲,如果我们将空参数传递给myMethod(),编译器将不知道应该使用哪个版本。此外,我们可以注意到该方法的返回类型对此问题没有影响。

4. isNull()调用不明确

让我们天真地尝试使用基本isNull() ArgumentMatcher模拟对myMethod()的调用,并使用空参数:

@Test
void givenMockedMyMethod_whenMyMethod_ThenMockedResult(@Mock MyClass myClass) {
    when(myClass.myMethod(isNull())).thenReturn(1);
}

假设我们将myMethod()定义为MyClass的类,我们通过测试的方法参数很好地注入了一个Mock的MyClass对象。我们还可以注意到,我们还没有向测试添加任何断言。让我们运行此代码:

java.lang.Error: Unresolved compilation problem: 
The method myMethod(Integer) is ambiguous for the type MyClass

我们可以看到,编译器无法决定使用哪个版本的myMethod(),因此会抛出错误。我们要强调的是,编译器的决定仅基于方法参数。由于我们在指令中编写了thenReturn(1),因此作为读者,我们可以猜测其意图是使用返回Integer的myMethod()版本。但是,编译器不会在其决策过程中使用指令的这一部分。

为了解决这个问题,我们需要使用重载的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("taketoday");
    assertEquals("taketoday", myClass.myMethod((MyOwnType) null));
}

最后,我们要注意,在断言中,我们也需要在对myMethod()的调用中给出null类型。否则,由于同样的原因,这将抛出错误!

5. any()的模糊调用

以同样的方式,我们可以尝试使用any() ArgumentMatcher模拟接收任何参数的myMethod()调用:

@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("taketoday");
    assertEquals("taketoday", myClass.myMethod((MyOwnType) null));
}

测试现在运行良好:我们成功消除了歧义的方法调用错误!

6. 总结

在本文中,我们了解了为什么使用Mockito框架时会遇到模糊方法调用错误。此外,我们还展示了解决问题的方法。

在现实项目中,当我们使用带有大量参数的重载方法时,最有可能出现此类问题,并且我们决定使用约束较少的isNull()或any() ArgumentMatcher,因为某些参数的值与我们的测试无关。在简单的情况下,大多数现代IDE甚至可以在我们需要运行测试之前指出问题。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章