应用Mockito的参数匹配器时遇到类型安全问题。

给定以下界面:

interface SomeInterface {

    int method(Object x);

}


我正在尝试模拟其唯一方法,并使用与匹配器类型不同的参数进行调用:

SomeInterface someInterface = mock(SomeInterface.class);
when(someInterface.method(argThat((ArgumentMatcher<Integer>) integer -> integer == 42))).thenReturn(42);
someInterface.method("X"); // Throws ClassCastException


但是方法调用someInterface.method("X")会产生异常,即:

java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer


但是,当我将lambda扩展到匿名类时,一切正常:

 SomeInterface someInterface = mock(SomeInterface.class);
 when(someInterface.method(argThat(new ArgumentMatcher<Integer>() {
     @Override
     public boolean matches(Integer integer) {
         return integer == 42;
     }
 }))).thenReturn(42);
 someInterface.method("X"); // OK, method invokes normally


从Mockito的资料中可以看到,matcher参数的类型与实际调用参数的类型进行了比较。如果实际参数不属于匹配器的方法参数类型的子类(因此无法分配给它),则不执行匹配:

private static boolean isCompatible(ArgumentMatcher<?> argumentMatcher, Object argument) {
        if (argument == null) {
            return true;
        } else {
            Class<?> expectedArgumentType = getArgumentType(argumentMatcher);
            return expectedArgumentType.isInstance(argument);
        }
    }


但是,似乎没有通过lambda的检查,这显然是因为在运行时无法检索lambda参数的实际类型(它始终只是Object类型)。我说得对吗?

我使用了模仿内核3.0.3。

我的Java配置:

java version "1.8.0_151"
Java(TM) SE Runtime Environment (build 1.8.0_151-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode)

最佳答案

我的第一个反应是“仅使用intThat(i -> i == 42)”,但是显然,该实现正在删除其具有ArgumentMatcher<Integer>的信息,并且此后依赖于无效的相同反射方法。

您无法以反射的方式获取lambda参数类型,并且已有问题解答解释了为什么它不可能甚至根本没有意图。请注意,这甚至不是lambda特有的。在某些情况下,具有普通类的类型也不可用。

最后,当它需要在使用端进行其他声明(如显式类型强制转换(ArgumentMatcher<Integer>))时,尝试使这种自动操作起作用没有任何意义。例如。代替

argThat((ArgumentMatcher<Integer>) integer -> integer == 42)


你也可以使用

argThat(obj -> obj instanceof Integer && (Integer)obj == 42)


甚至

argThat(Integer.valueOf(42)::equals)


但是,当我们看这些琐碎的示例时,eq(42)甚至可以做到甚至when(someInterface.method(42)).thenReturn(42)

但是,当您经常需要在这样的上下文中使用更广泛的参数类型来匹配复杂的整数表达式时,最接近您原始尝试的解决方案是声明一个可重用的固定类型匹配器接口。

interface IntArgMatcher extends ArgumentMatcher<Integer> {
  @Override boolean matches(Integer arg0);
}


和使用

when(someInterface.method(argThat((IntArgMatcher)i -> i == 42))).thenReturn(42);


i == 42用作更复杂表达式的占位符

08-26 16:46
查看更多