考虑这段代码:
public class DummyClass {
public List<? extends Number> dummyMethod() {
return new ArrayList<Integer>();
}
}
public class DummyClassTest {
public void testMockitoWithGenerics() {
DummyClass dummyClass = Mockito.mock(DummyClass.class);
List<? extends Number> someList = new ArrayList<Integer>();
Mockito.when(dummyClass.dummyMethod()).thenReturn(someList); //Compiler complains about this
}
}
编译器抱怨试图对
dummyMethod()
的行为进行存根的行。有关如何处理返回带有有界通配符的类型的存根方法的任何指示吗?
您还可以使用非类型安全方法doReturn来实现此目的,
@Test
public void testMockitoWithGenerics()
{
DummyClass dummyClass = Mockito.mock(DummyClass.class);
List<? extends Number> someList = new ArrayList<Integer>();
Mockito.doReturn(someList).when(dummyClass).dummyMethod();
Assert.assertEquals(someList, dummyClass.dummyMethod());
}
如 Mockito 的谷歌群组中讨论。
虽然这比
thenAnswer
更简单,但请再次注意,它不是类型安全的。如果您担心类型安全,Millhouse 的 answer 是正确的。
需要明确的是,这是观察到的编译器错误,
The method thenReturn(List<capture#1-of ? extends Number>) in the type OngoingStubbing<List<capture#1-of ? extends Number>> is not applicable for the arguments (List<capture#2-of ? extends Number>)
我相信编译器在
when
调用期间分配了第一个通配符类型,然后无法确认 thenReturn
调用中的第二个通配符类型是否相同。
看起来
thenAnswer
不会遇到此问题,因为它接受通配符类型,而 thenReturn
则采用非通配符类型,必须捕获该类型。来自 Mockito 的 OngoingStubbing,
OngoingStubbing<T> thenAnswer(Answer<?> answer);
OngoingStubbing<T> thenReturn(T value);
我假设您希望能够加载一些已知值;这是一种使用
someList
和模板化辅助方法来保持所有内容类型安全的方法:Answer<T>
@Test
public void testMockitoWithGenericsUsingAnswer()
{
DummyClass dummyClass = Mockito.mock(DummyClass.class);
Answer<List<Integer>> answer = setupDummyListAnswer(77, 88, 99);
Mockito.when(dummyClass.dummyMethod()).thenAnswer(answer);
...
}
private <N extends Number> Answer<List<N>> setupDummyListAnswer(N... values) {
final List<N> someList = new ArrayList<N>();
someList.addAll(Arrays.asList(values));
Answer<List<N>> answer = new Answer<List<N>>() {
public List<N> answer(InvocationOnMock invocation) throws Throwable {
return someList;
}
};
return answer;
}
引起的,而是由
java.util.List
引起的。因此,我的小助手方法允许任何类型 com.google.common.base.Optional
而不仅仅是 T
:List<T>
使用这个辅助方法你可以写:
public static <T> Answer<T> createAnswer(final T value) {
Answer<T> dummy = new Answer<T>() {
@Override
public T answer(InvocationOnMock invocation) throws Throwable {
return value;
}
};
return dummy;
}
这个编译得很好,并且与
Mockito.when(dummyClass.dummyMethod()).thenAnswer(createAnswer(someList));
方法做同样的事情。
有人知道 Java 编译器发出的错误是编译器错误还是代码真的不正确?
fikovnik 的评论 转换为此处的答案,以提高其可见性,因为我认为这是使用 Java 8+ 的最优雅的解决方案。
Mockito文档建议使用thenReturn(...)
(如已接受的答案中所建议)仅作为最后的手段。
相反,为了避免问题中描述的编译器错误,推荐的 Mockito
doReturn()
方法可以与
when()
和 lambda (而不是辅助方法)一起使用:thenAnswer()
BDDMockito.willReturn(someList).given(dummyClass).dummyMethod();
--List
dummyMethod() {...}--合规解决方案
? extends
注意:
List<Number> dummyMethod() {
List<Integer> result = ...
return List.copyOf(result);
}
实现是高性能的
如果你的集合是使用List.of、Stream.toList等创建的,有效地进行强制转换而不是真正的副本。查看其源代码:src 1,src 2。 理由:
在返回类型中使用像List.copyOf()
这样的通配符是一个关键的“代码味道”。因为会迫使您的客户端代码与通配符类型声明作斗争,就像您使用 Mockito 的示例一样。
声纳代码质量不要使用 <> 通配符类型作为返回类型,因为这会强制客户端代码使用通配符。如果使用得当,通配符对于类的用户来说几乎是不可见的。 如果类的用户必须考虑通配符类型,则其 API 可能有问题。
强烈建议不使用通配符类型作为返回类型。由于类型推断规则相当复杂,因此该 API 的用户不太可能知道如何正确使用它。
...协方差对于方法的返回类型无效,因为它不是输入位置。使其逆变也没有效果,因为它是返回值的接收者,而返回值必须是逆变的(Java 中的使用站点差异)。因此,包含通配符的返回类型通常是一个错误。