Mockito:使用有界通配符返回类型的存根方法

问题描述 投票:0回答:6

考虑这段代码:

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()
的行为进行存根的行。有关如何处理返回带有有界通配符的类型的存根方法的任何指示吗?

java unit-testing generics mockito bounded-wildcard
6个回答
237
投票

您还可以使用非类型安全方法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);

39
投票

我假设您希望能够加载一些已知值;这是一种使用

someList
和模板化辅助方法来保持所有内容类型安全的方法:

Answer<T>



22
投票
@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 编译器发出的错误是编译器错误还是代码真的不正确?


16
投票

fikovnik 的评论 转换为此处的答案,以提高其可见性,因为我认为这是使用 Java 8+ 的最优雅的解决方案。

Mockito文档

建议使用thenReturn(...)(如已接受的答案中所建议)仅作为最后的手段。


相反,为了避免问题中描述的编译器错误,推荐的 Mockito

doReturn()

方法可以与

when()
和 lambda (而不是辅助方法)一起使用:

thenAnswer()



1
投票

正如类似问题的

这个答案

所示,您还可以使用以下内容: Mockito.when(mockedClass.mockedMethod()).thenAnswer(x -> resultList)



0
投票
BDDMockito.willReturn(someList).given(dummyClass).dummyMethod();

不合规代码示例:

--List
dummyMethod() {...}--

合规解决方案

? extends

注意:
List<Number> dummyMethod() { List<Integer> result = ... return List.copyOf(result); }

实现是高性能的

如果
你的集合是使用List.of、Stream.toList等创建的,有效地进行强制转换而不是真正的副本。查看其源代码:src 1src 2

理由:

在返回类型中使用像List.copyOf()这样的通配符是一个关键的“代码味道”。因为会迫使您的客户端代码与通配符类型声明作斗争,就像您使用 Mockito 的示例一样。

参考资料:

Joshua Bloch“
    Effective Java
  • ”第 31 条:

不要使用 <> 通配符类型作为返回类型,因为这会强制客户端代码使用通配符。如果使用得当,通配符对于类的用户来说几乎是不可见的。 如果类的用户必须考虑通配符类型,则其 API 可能有问题。

声纳代码质量
    规则java:S1452
  • 类别:
      代码异味
    • ;严重性:严重
强烈建议

使用通配符类型作为返回类型。由于类型推断规则相当复杂,因此该 API 的用户不太可能知道如何正确使用它。

...协方差对于方法的返回类型无效,因为它不是输入位置。使其逆变也没有效果,因为它是返回值的接收者,而返回值必须是逆变的(Java 中的使用站点差异)。因此,

包含通配符的返回类型通常是一个错误。

© www.soinside.com 2019 - 2024. All rights reserved.