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

发布于 2024-12-03 23:54:42 字数 693 浏览 1 评论 0 原文

考虑以下代码:

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

Consider this code:

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
    }
}

The compiler complains about the line that's trying to stub the behavior for dummyMethod(). Any pointers on how one goes about stubbing methods that return a type with bounded wild-cards?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

甲如呢乙后呢 2024-12-10 23:54:42

您还可以使用非类型安全方法 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 的 google 上讨论团体。

虽然这比 thenAnswer 更简单,但请再次注意,它不是类型安全的。如果您担心类型安全,millhouse 的答案是正确的。

其他详细信息

需要明确的是,这里是观察到的编译器错误,

OngoingStubbing)扩展编号>>不适用于参数 (List)

我相信编译器在 when 调用期间分配了第一个通配符类型,然后无法确认thenReturn 调用中的第二个通配符类型是相同的。

看起来 thenAnswer 不会遇到此问题,因为它接受通配符类型,而 thenReturn 则采用非通配符类型,必须捕获该类型。来自 Mockito 的 正在进行的存根,

OngoingStubbing<T> thenAnswer(Answer<?> answer);
OngoingStubbing<T> thenReturn(T value);

You can also use the non-type safe method doReturn for this purpose,

@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());
}

as discussed on Mockito's google group.

While this is simpler than thenAnswer, again note that it is not type safe. If you're concerned about type safety, millhouse's answer is correct.

Additional Details

To be clear, here's the observed compiler error,

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>)

I believe the compiler has assigned the first wildcard type during the when call and then cannot confirm that the second wildcard type in the thenReturn call is the same.

It looks like thenAnswer doesn't run into this issue because it accepts a wildcard type while thenReturn takes a non-wildcard type, which must be captured. From Mockito's OngoingStubbing,

OngoingStubbing<T> thenAnswer(Answer<?> answer);
OngoingStubbing<T> thenReturn(T value);
拥抱影子 2024-12-10 23:54:42

我假设您希望能够加载一些已知值的 someList ;这是一种使用 Answer 和模板化辅助方法来保持所有内容类型安全的方法:

@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;
}

I'm assuming you want to be able to load up someList with some known values; here's an approach that uses Answer<T> together with a templated helper method to keep everything type-safe:

@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;
}
千纸鹤带着心事 2024-12-10 23:54:42

我昨天也碰到同样的事情。 @nondescript1 和 @millhouse 的回答都帮助我找到了解决方法。我几乎使用了与 @millhouse 相同的代码,只是我让它稍微更通用一些,因为我的错误不是由 java.util.List 引起的,而是由 com 引起的.google.common.base.可选。因此,我的小辅助方法允许任何类型 T 而不仅仅是 List

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));

这编译得很好,并且执行与thenReturn(...) 方法。

有人知道 Java 编译器发出的错误是编译器错误还是代码确实不正确?

I hit the same thing yesterday. Both answers from @nondescript1 and @millhouse helped me to figure out a workaround. I've pretty much used the same code as @millhouse, except that I made it slightly more generic, because my error wasn't caused by a java.util.List, but the com.google.common.base.Optional. My little helper method therefore allows for any type T and not just 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;
}

With this helper method you could write:

Mockito.when(dummyClass.dummyMethod()).thenAnswer(createAnswer(someList));

This compiles just fine and does the same thing as the thenReturn(...) method.

Does someone know if the error that the Java compiler emits is a compiler bug or if the code is really incorrect?

凉墨 2024-12-10 23:54:42

我正在转向fikovnik 的评论 放入此处的答案中,以使其更具可见性,因为我认为这是使用 Java 8+ 的最优雅的解决方案。

Mockito 文档建议仅将 doReturn() (如已接受答案中的建议)用作最后的手段。

相反,为了规避问题中描述的编译器错误,推荐的 Mockito when() 方法可以与 thenAnswer() 和 lambda (而不是辅助方法)一起使用:

Mockito.when(mockedClass.mockedMethod()).thenAnswer(x -> resultList)

I'm turning fikovnik's comment into an answer here to give it more visibility as I think it's the most elegant solution using Java 8+.

The Mockito documentation recommends using doReturn() (as suggested in the accepted answer) only as a last resort.

Instead, to circumevent the compiler error described in the question, the recommended Mockito when() approach can be used with thenAnswer() and a lambda (instead of a helper method):

Mockito.when(mockedClass.mockedMethod()).thenAnswer(x -> resultList)
也只是曾经 2024-12-10 23:54:42

尽管 Marek Radonsky 提出的实用方法有效,但还有另一种选择,甚至不需要 fikovnik 建议的(恕我直言,看起来很奇怪)lambda 表达式:

这个类似问题的答案显示,您还可以使用以下内容:

BDDMockito.willReturn(someList).given(dummyClass).dummyMethod();

Although the utility method proposed by Marek Radonsky works, there is also an other option which doesn't even require the (IMHO strange looking) lambda expression fikovnik suggested:

As this answer to a similar question shows, you can also use the following:

BDDMockito.willReturn(someList).given(dummyClass).dummyMethod();
失退 2024-12-10 23:54:42

删除<代码>?从您的 DummyClass 返回类型扩展 。

不合规代码示例:

--列表 dummyMethod() {...}--

合规解决方案

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

注意:List.copyOf() 实现是高性能的如果您的集合是使用 List 创建的.of、Stream.toList 等,有效地进行强制转换而不是真正的副本。查看其源代码: src 1src 2

理由:使用通配符,例如 是一个关键的“Code Smell”。因为会迫使您的客户端代码与通配符类型声明作斗争,就像您使用 Mockito 的示例一样。

参考文献:

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

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

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

Remove ? extends from your DummyClass return type.

Noncompliant Code Example:

--List<? extends Number> dummyMethod() {...}--

Compliant Solution

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

NOTE: List.copyOf() implementation is performant if your collection is created with List.of, Stream.toList, etc., effectively doing a cast rather than a real copy. See its source code: src 1, src 2.

JUSTIFICATION: Using a wildcard like <? extends Number> in the return type is a critical "Code Smell". Because would force your client code to struggle with wildcard-type declarations, as in your example with Mockito.

REFERENCEs:

Do not use <> wildcard types as return types, because it would force client code to use wildcards. Properly used, wildcards are nearly invisible to the user of a class. If the user of a class has to think about wildcard types, there is probably something wrong with its API.

  • Sonar code quality Rule java:S1452
    • category: Code smell; severity: Critical

It is highly recommended not to use wildcard types as return types. Because the type inference rules are fairly complex it is unlikely the user of that API will know how to use it correctly.

...covariance is ineffective for the return type of a method since it is not an input position. Making it contravariant also has no effect since it is the receiver of the return value which must be contravariant (use-site variance in Java). Consequently, a return type containing wildcards is generally a mistake.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文