Java 泛型 - 方法不适用于 Mockito 生成的存根
Java Generics - method not applicable to Mockito generated stub
我有一个 ThingsProvider
接口,我正在尝试使用 Mockito 进行测试,(简化版)定义如下:
interface ThingsProvider {
Iterable<? extends Thing> getThings()
}
现在,当我要使用 Mockito 对其进行测试时,我正在执行以下操作(再次针对问题进行了简化):
ThingsProvider thingsProvider = mock(ThingsProvider.class);
List<Thing> things = Arrays.asList(mock(Thing.class));
when(thingsProvider.getThings()).thenReturn(things); // PROBLEM IS HERE
编译错误信息:The method thenReturn(Iterable<capture#11-of ? extends Thing>) in the type OngoingStubbing<Iterable<capture#11-of ? extends Thing>> is not applicable for the arguments (List<Thing>)
现在,纯粹为了进行测试,我将最后一行更改为
when(thingsProvider.getThings()).thenReturn((List)things); // HAHA take THAT generics!
...但这在非测试代码中显然是不好的。
我的问题:
- 为什么这是一个错误?我显然 returning 扩展了
Thing
的对象,这是界面所期望的。
- 有没有更好的方法解决这个问题?也许以不同的方式定义我的界面?到目前为止,除了测试之外,我还没有 运行 解决这个问题...
关于 #2 - 我不简单地 returning Iterable<Thing>
的主要原因是有几个不同的扩展,具体类型中隐藏了 return特定的子类型,我最终遇到了 Type mismatch: cannot convert from Iterable<MagicalThing> to Iterable<Thing>
之类的问题 - 也许解决方案是解决 这个 问题的更好方法?
对于那些不太熟悉 Mockito 的人,下面是 #1 的更纯粹的 Java 版本:
public static void main(String...args) {
List<Integer> ints = Arrays.asList(1,2,3);
blah(ints);
Foo<Number> foo1 = new Foo<Number>();
foo1.bar(ints); // This works
Foo<? extends Number> foo2 = new Foo<Number>();
foo2.bar(ints); // NO COMPILEY!
}
private static void blah(List<? extends Number> numberList) {
// something
}
public static class Foo<T> {
public Object bar(List<? extends T> tList) {
return null;
}
}
重构为两步
OngoingStubbing<Iterable<? extends Thing>> stub = when(thingsProvider.getThings());
stub.thenReturn(things);
或使用建议的
Mockito.<Iterable<? extends Thing>>when(thingsProvider.getThings())
如您所见,return 类型中的通配符对于子类实现非常方便。
不过,这对方法的调用者来说没有太大区别;如果您愿意,可以将其更改为 Iterable<Thing>
;它在 javadoc 上更简单,但以牺牲子类实现者为代价。如有必要,子类可以进行强制转换,例如List<MyThing> => Iterable<Thing>
,感谢擦除。
你的问题的原因是通配符捕获;基本上每个表达式都先通过通配符捕获,然后再评估上下文表达式。在方法调用中
foo( arg )
arg
总是在方法 applicability/overloading/inference 完成之前首先捕获通配符。在您的情况下,更通用的类型 Iterable<? extends Thing>
丢失,变为 Iterable<CAP#>
.
通常,通配符捕获不会造成任何问题;但是 Mockito 语义并不常见。
第一个解决方案是避免类型推断;显式提供类型参数
Mockito.<Iterable<? extends Thing>>when(...
或者按照 Delimanolis 的建议,使用目标类型来限制推理(在 java8+ 中)
OngoingStubbing<Iterable<? extends Thing>> stub = when(...
看来 lambda 推理可能对这种情况有所帮助
static <T> OngoingStubbing<T> whenX( Supplier<T> sup )
{
return Mockito.when( sup.get() );
}
whenX(thingsProvider::getThings).thenReturn(things);
// T = Iterable<? extends Thing>
而且 - 如果您的界面足够简单,直接实现它而不是模拟它:)
List<Thing> things = ...;
ThingsProvider thingsProvider = ()->things;
我有一个 ThingsProvider
接口,我正在尝试使用 Mockito 进行测试,(简化版)定义如下:
interface ThingsProvider {
Iterable<? extends Thing> getThings()
}
现在,当我要使用 Mockito 对其进行测试时,我正在执行以下操作(再次针对问题进行了简化):
ThingsProvider thingsProvider = mock(ThingsProvider.class);
List<Thing> things = Arrays.asList(mock(Thing.class));
when(thingsProvider.getThings()).thenReturn(things); // PROBLEM IS HERE
编译错误信息:The method thenReturn(Iterable<capture#11-of ? extends Thing>) in the type OngoingStubbing<Iterable<capture#11-of ? extends Thing>> is not applicable for the arguments (List<Thing>)
现在,纯粹为了进行测试,我将最后一行更改为
when(thingsProvider.getThings()).thenReturn((List)things); // HAHA take THAT generics!
...但这在非测试代码中显然是不好的。
我的问题:
- 为什么这是一个错误?我显然 returning 扩展了
Thing
的对象,这是界面所期望的。 - 有没有更好的方法解决这个问题?也许以不同的方式定义我的界面?到目前为止,除了测试之外,我还没有 运行 解决这个问题...
关于 #2 - 我不简单地 returning Iterable<Thing>
的主要原因是有几个不同的扩展,具体类型中隐藏了 return特定的子类型,我最终遇到了 Type mismatch: cannot convert from Iterable<MagicalThing> to Iterable<Thing>
之类的问题 - 也许解决方案是解决 这个 问题的更好方法?
对于那些不太熟悉 Mockito 的人,下面是 #1 的更纯粹的 Java 版本:
public static void main(String...args) {
List<Integer> ints = Arrays.asList(1,2,3);
blah(ints);
Foo<Number> foo1 = new Foo<Number>();
foo1.bar(ints); // This works
Foo<? extends Number> foo2 = new Foo<Number>();
foo2.bar(ints); // NO COMPILEY!
}
private static void blah(List<? extends Number> numberList) {
// something
}
public static class Foo<T> {
public Object bar(List<? extends T> tList) {
return null;
}
}
重构为两步
OngoingStubbing<Iterable<? extends Thing>> stub = when(thingsProvider.getThings());
stub.thenReturn(things);
或使用
Mockito.<Iterable<? extends Thing>>when(thingsProvider.getThings())
如您所见,return 类型中的通配符对于子类实现非常方便。
不过,这对方法的调用者来说没有太大区别;如果您愿意,可以将其更改为 Iterable<Thing>
;它在 javadoc 上更简单,但以牺牲子类实现者为代价。如有必要,子类可以进行强制转换,例如List<MyThing> => Iterable<Thing>
,感谢擦除。
你的问题的原因是通配符捕获;基本上每个表达式都先通过通配符捕获,然后再评估上下文表达式。在方法调用中
foo( arg )
arg
总是在方法 applicability/overloading/inference 完成之前首先捕获通配符。在您的情况下,更通用的类型 Iterable<? extends Thing>
丢失,变为 Iterable<CAP#>
.
通常,通配符捕获不会造成任何问题;但是 Mockito 语义并不常见。
第一个解决方案是避免类型推断;显式提供类型参数
Mockito.<Iterable<? extends Thing>>when(...
或者按照 Delimanolis 的建议,使用目标类型来限制推理(在 java8+ 中)
OngoingStubbing<Iterable<? extends Thing>> stub = when(...
看来 lambda 推理可能对这种情况有所帮助
static <T> OngoingStubbing<T> whenX( Supplier<T> sup )
{
return Mockito.when( sup.get() );
}
whenX(thingsProvider::getThings).thenReturn(things);
// T = Iterable<? extends Thing>
而且 - 如果您的界面足够简单,直接实现它而不是模拟它:)
List<Thing> things = ...;
ThingsProvider thingsProvider = ()->things;