【问题标题】:Using Mockito to mock classes with generic parameters使用 Mockito 模拟具有泛型参数的类
【发布时间】:2009-10-30 22:48:52
【问题描述】:

有没有一种简洁的方法来模拟具有泛型参数的类?假设我必须模拟一个类Foo<T>,我需要将它传递给一个需要Foo<Bar> 的方法。我可以很容易地做到以下几点:

Foo mockFoo = mock(Foo.class);
when(mockFoo.getValue).thenReturn(new Bar());

假设getValue() 返回泛型类型T。但是当我后来将它传递给一个期望Foo<Bar>的方法时,它就会有小猫。投射是唯一的方法吗?

【问题讨论】:

  • 为什么要使用 foo 和 bar 而不是更有意义的名称。只是给很多人造成了一大堆混乱。
  • @Kaigo 在编程示例中使用 foo、bar 和 baz 是非常典型的,尤其是在匿名现实世界示例以隐藏机密细节时。现在,我更关心的是 OP 使用了“那将会有小猫”这个短语......实际上之前从未听过任何人这么说;)
  • 每个人都知道“养小猫”对代码来说是一件坏事,即使它对猫来说非常好。 :--)

标签: java generics mockito


【解决方案1】:

我认为你确实需要投射它,但应该不会太糟糕:

Foo<Bar> mockFoo = (Foo<Bar>) mock(Foo.class);
when(mockFoo.getValue()).thenReturn(new Bar());

【讨论】:

  • 是的,但您仍然收到警告。这样可以避免警告吗?
  • @SuppressWarnings("unchecked")
  • 我认为这是完全可以接受的,因为我们在单元测试中讨论的是模拟对象。
  • @demaniak 它根本不起作用。不能在该上下文中使用参数匹配器。
  • @demaniak 编译得很好,但是在运行测试时会抛出 InvalidUseOfMatchersException (这是一个 RuntimeException)
【解决方案2】:

另一种解决方法是改用@Mock 注释。 并非在所有情况下都有效,但看起来更性感:)

这是一个例子:

@RunWith(MockitoJUnitRunner.class)
public class FooTests {

    @Mock
    public Foo<Bar> fooMock;
    
    @Test
    public void testFoo() {
        when(fooMock.getValue()).thenReturn(new Bar());
    }
}

MockitoJUnitRunner 初始化带有 @Mock 注释的字段。

【讨论】:

  • 这在 1.9.5 中已弃用。 :( 对我来说似乎更干净。
  • @CodeNovitiate 我在 1.9.5 中的 MockitoJUnitRunner 和 Mock 上找不到任何弃用注释。那么,什么是弃用的? (是的,org.mockito.MockitoAnnotations.Mock 已弃用,但您应该改用 org.mockito.Mock)
  • 干得好,这对我来说非常有效。它不仅仅是“更性感”,它在不使用SuppressWarnings 的情况下避免了警告。警告的存在是有原因的,最好不要习惯于压制它们。谢谢!
  • 有一点我不喜欢使用@Mock而不是mock():在构建期间字段仍然为空,所以我当时无法插入依赖项并且无法制作字段最后。前者当然可以通过@Before-annotated 方法解决。
  • 启动只需调用 MockitoAnnotations.initMocks(this);
【解决方案3】:

您始终可以创建一个满足您要指定的泛型类型的中间类/接口。例如,如果 Foo 是一个接口,您可以在测试类中创建以下接口。

private interface FooBar extends Foo<Bar>
{
}

在 Foo 是非最终类的情况下,您可以使用以下代码扩展该类并执行相同的操作:

public class FooBar extends Foo<Bar>
{
}

然后您可以使用以下代码使用上述任一示例:

Foo<Bar> mockFoo = mock(FooBar.class);
when(mockFoo.getValue()).thenReturn(new Bar());

【讨论】:

  • 如果Foo 是一个接口或非最终类,这似乎是一个相当优雅的解决方案。谢谢。
  • 我更新了答案以包括非最终课程的示例。理想情况下,您将针对接口进行编码,但情况并非总是如此。好收获!
【解决方案4】:

创建一个测试实用方法。如果您多次需要它,它特别有用。

@Test
public void testMyTest() {
    // ...
    Foo<Bar> mockFooBar = mockFoo();
    when(mockFooBar.getValue).thenReturn(new Bar());

    Foo<Baz> mockFooBaz = mockFoo();
    when(mockFooBaz.getValue).thenReturn(new Baz());

    Foo<Qux> mockFooQux = mockFoo();
    when(mockFooQux.getValue).thenReturn(new Qux());
    // ...
}

@SuppressWarnings("unchecked") // still needed :( but just once :)
private <T> Foo<T> mockFoo() {
    return mock(Foo.class);
}

【讨论】:

  • 可以扩展您的答案以使通用实用程序方法传入您要模拟的类。
  • @WilliamDutton static &lt;T&gt; T genericMock(Class&lt;? super T&gt; classToMock) { return (T)mock(classToMock); } 它甚至不需要一个单一的抑制 :) 但要小心,Integer num = genericMock(Number.class) 编译,但抛出 ClassCastException。这仅对最常见的G&lt;P&gt; mock = mock(G.class) 情况有用。
【解决方案5】:

我同意不应抑制类或方法中的警告,因为可能会忽略其他意外抑制的警告。但是恕我直言,禁止仅影响一行代码的警告是绝对合理的。

@SuppressWarnings("unchecked")
Foo<Bar> mockFoo = mock(Foo.class);

【讨论】:

    【解决方案6】:

    正如提到的其他答案,没有不安全的泛型访问和/或抑制泛型警告的直接使用 mock()spy() 方法的好方法。

    Mockito 项目 (#1531) 目前存在一个未解决的问题,以添加对使用 mock()spy() 方法的支持而不会出现泛型警告。该问题于 2018 年 11 月开放,但没有任何迹象表明它会被优先处理。根据 Mockito 贡献者的一位 cmet 的说法:

    鉴于.class 不能很好地与泛型配合使用,我认为我们无法在 Mockito 中提供任何解决方案。您已经可以使用@Mock(JUnit5 扩展还允许方法参数@Mocks),这应该是一个合适的替代方案。因此,我们可以让这个问题保持开放,但它不太可能得到修复,因为@Mock 是一个更好的 API。

    【讨论】:

    • 在某些情况下spy() 没问题,因为您可以将实例传递给它。显然不能涵盖所有基础,但如果您可以实例化一个通用实例,那么您就可以开始了。
    【解决方案7】:

    对于 JUnit5,我认为最好的方法是在方法参数或字段中使用 @Mock @ExtendWith(MockitoExtension.class)。

    以下示例演示了 Hamcrest 匹配器。

    package com.vogella.junit5;                                                                    
                                                                                                   
    import static org.hamcrest.MatcherAssert.assertThat;                                           
    import static org.hamcrest.Matchers.hasItem;                                                   
    import static org.mockito.Mockito.verify;                                                      
                                                                                                   
    import java.util.Arrays;                                                                       
    import java.util.List;                                                                         
                                                                                                   
    import org.junit.jupiter.api.Test;                                                             
    import org.junit.jupiter.api.extension.ExtendWith;                                             
    import org.mockito.ArgumentCaptor;                                                             
    import org.mockito.Captor;                                                                     
    import org.mockito.Mock;                                                                       
    import org.mockito.junit.jupiter.MockitoExtension;                                             
                                                                                                   
    @ExtendWith(MockitoExtension.class)                                                            
    public class MockitoArgumentCaptureTest {                                                      
                                                                                                   
                                                                                                   
        @Captor                                                                                    
        private ArgumentCaptor<List<String>> captor;                                               
                                                                                                   
        @Test                                                                                      
        public final void shouldContainCertainListItem(@Mock List<String> mockedList) {            
            var asList = Arrays.asList("someElement_test", "someElement");                         
            mockedList.addAll(asList);                                                             
                                                                                                   
            verify(mockedList).addAll(captor.capture());                                           
            List<String> capturedArgument = captor.getValue();                                     
            assertThat(capturedArgument, hasItem("someElement"));                                  
        }                                                                                          
    }                                                                                              
                                                                                                  
    

    有关所需的 Maven/Gradle 依赖项,请参阅 https://www.vogella.com/tutorials/Mockito/article.html

    【讨论】:

      【解决方案8】:

      这是一个有趣的例子:方法接收泛型集合并返回相同基类型的泛型集合。例如:

      Collection<? extends Assertion> map(Collection<? extends Assertion> assertions);
      

      可以结合 Mockito anyCollectionOf 匹配器和答案来模拟此方法。

      when(mockedObject.map(anyCollectionOf(Assertion.class))).thenAnswer(
           new Answer<Collection<Assertion>>() {
               @Override
               public Collection<Assertion> answer(InvocationOnMock invocation) throws Throwable {
                   return new ArrayList<Assertion>();
               }
           });
      

      【讨论】:

        【解决方案9】:

        JUnit5:在测试类上使用@ExtendWith(MockitoExtension.class),然后添加这个字段:

        @Mock
        Foo<Bar> barMock;
        

        【讨论】:

        • 此解决方案已在其他几个答案中详细说明,我不确定它如何增加任何价值。
        • @Pyves 这涵盖了不适用于 @RunWith(MockitoJUnitRunner.class) 的 JUnit5
        • 我看到至少一个已经涵盖@ExtendWith(MockitoExtension.class) 的其他答案,以及其他无论使用的 JUnit 版本如何都有效的答案。无论如何,这并不是问题/答案的重点。
        【解决方案10】:

        为什么不使用间谍

        var mock = spy(new Foo<Bar>());
        when(mockFoo.getValue()).thenReturn(new Bar());
        

        【讨论】:

        • 这仍然会导致警告,正如其他答案所强调的那样。
        猜你喜欢
        • 2012-04-12
        • 1970-01-01
        • 2020-08-28
        • 2019-04-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多