使用Mockito模拟具有泛型参数的类

Tim*_*ons 261 java generics mockito

是否有一种使用泛型参数模拟类的干净方法?假设我必须模拟一个Foo<T>我需要传递给期望a的方法的类Foo<Bar>.我可以很容易地做到以下几点:

Foo mockFoo = mock(Foo.class);
when(mockFoo.getValue).thenReturn(new Bar());
Run Code Online (Sandbox Code Playgroud)

假设getValue()返回泛型类型T.但是当我稍后将它传递给期望的方法时,那将会有小猫Foo<Bar>.铸造是这样做的唯一方法吗?

小智 261

另一种方法是使用@Mock注释.在所有情况下都不起作用,但看起来更性感:)

这是一个例子:

@RunWith(MockitoJUnitRunner.class)
public class FooTests {

    @Mock
    public Foo<Bar> fooMock;

    @Test
    public void testFoo() {
        when(fooMock.getValue()).thenReturn(new Bar());
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • @CodeNovitiate我在1.9.5中找不到MockitoJUnitRunner和Mock上的任何弃用注释.那么,什么是弃用的?(是的,org.mockito.MockitoAnnotations.Mock已弃用,但您应该使用org.mockito.Mock) (11认同)
  • 干得好,这对我来说非常合适.这不仅仅是"性感",它避免了在不使用"SuppressWarnings"的情况下发出警告.警告存在是有原因的,最好不要养成压制它们的习惯.谢谢! (9认同)
  • 你能给出一个适用于OP场景的例子吗? (4认同)
  • 有一件事我不喜欢使用`@Mock`而不是`mock()`:在构造期间字段仍为null,因此我不能在那时插入依赖项并且不能使字段成为最终字段.前者可以通过`@ Before`-annotated方法解决. (4认同)
  • 这在1.9.5中已弃用.:(似乎对我来说更清洁. (3认同)
  • 如需启动,请致电MockitoAnnotations.initMocks(this); (3认同)
  • 仅供参考:Mockito 只是在做演员并压制幕后的警告。我也更喜欢这个,但是,从功能上讲,它并没有更好。当我静态地知道我需要的所有模拟时,我使用 `@Mock` + `MockitoAnnotations.initMocks(this)`。当我需要动态/即时生成模拟时,我使用 `mock(Class.class)` + 转换。 (2认同)

Joh*_*ett 253

我认为你确实需要施展它,但它不应该太糟糕:

Foo<Bar> mockFoo = (Foo<Bar>) mock(Foo.class);
when(mockFoo.getValue()).thenReturn(new Bar());
Run Code Online (Sandbox Code Playgroud)

  • 它有效,但它不是那么好.还有其他选择吗? (72认同)
  • 是的,但你仍然有警告.这有可能避免警告吗? (27认同)
  • 我认为这是完全可以接受的,因为我们在单元测试中讨论模拟对象. (16认同)
  • @SuppressWarnings( "未登记") (11认同)
  • @demanak这会编译得很好,但是在运行测试时它会抛出 InvalidUseOfMatchersException (这是一个 RuntimeException ) (2认同)

dsi*_*ton 35

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

private interface FooBar extends Foo<Bar>
{
}
Run Code Online (Sandbox Code Playgroud)

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

public class FooBar extends Foo<Bar>
{
}
Run Code Online (Sandbox Code Playgroud)

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

Foo<Bar> mockFoo = mock(FooBar.class);
when(mockFoo.getValue()).thenReturn(new Bar());
Run Code Online (Sandbox Code Playgroud)

  • 如果`Foo`是一个接口或非最终类,这似乎是一个相当优雅的解决方案.谢谢. (4认同)

acd*_*ior 15

创建测试实用程序方法.如果你不止一次需要它特别有用.

@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);
}
Run Code Online (Sandbox Code Playgroud)

  • @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)”情况有用。 (2认同)

Tob*_*ann 11

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

@SuppressWarnings("unchecked")
Foo<Bar> mockFoo = mock(Foo.class);
Run Code Online (Sandbox Code Playgroud)


vog*_*lla 11

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

以下示例通过 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"));                                  
    }                                                                                          
}                                                                                              
                                                                                              
Run Code Online (Sandbox Code Playgroud)

请参阅https://www.vogella.com/tutorials/Mockito/article.html了解所需的 Maven/Gradle 依赖项。


M. *_*tin 6

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

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

鉴于.class泛型不能很好地发挥作用,我认为我们在 Mockito 中没有任何解决方案。您已经可以做到@Mock(JUnit5 扩展也允许方法参数@Mock),这应该是一个合适的替代方案。因此,我们可以保持这个问题开放,但它不太可能被修复,因为这@Mock是一个更好的 API。


mis*_*que 5

所以你有这个:

Foo mockFoo = mock(Foo.class);
Run Code Online (Sandbox Code Playgroud)

修复它的方法,从我最不喜欢的到最喜欢的:

  1. 使用@SuppressWarnings("unchecked")注释。并不能真正解决问题,但您将不再收到警告。
@SuppressWarnings("unchecked")
Foo mockFoo = mock(Foo.class);
when(mockFoo.getValue).thenReturn(new Bar());
Run Code Online (Sandbox Code Playgroud)
  1. 投射它。不幸的是,尽管它仍然发出警告。所以你还需要在这里使用注释:
@SuppressWarnings("unchecked")
Foo<Bar> mockFoo = (Foo<Bar>) mock(Foo.class);
when(mockFoo.getValue).thenReturn(new Bar());
Run Code Online (Sandbox Code Playgroud)
  1. 使用@Mock注解。不会有任何警告。这里,when实际测试时可以添加。
@Mock
public Foo<Bar> fooMock;
Run Code Online (Sandbox Code Playgroud)
  1. 使用@MockBean注解。这将直接创建一个模拟 bean。没有警告。
@MockBean
public Foo<Bar> fooMock;
Run Code Online (Sandbox Code Playgroud)