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

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

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

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

249819 次浏览

我认为你确实需要施放它,但这应该不会太糟:

Foo<Bar> mockFoo = (Foo<Bar>) mock(Foo.class);
when(mockFoo.getValue()).thenReturn(new Bar());
另一种方法是使用@Mock注释。 不是在所有情况下都有效,但看起来更性感:)

这里有一个例子:

@RunWith(MockitoJUnitRunner.class)
public class FooTests {


@Mock
public Foo<Bar> fooMock;
    

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

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

您总是可以创建一个中间类/接口来满足您想要指定的泛型类型。例如,如果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());

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

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

这个方法可以通过组合Mockito anyCollectionOf匹配器和Answer来模拟。

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

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

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

我同意人们不应该在类或方法中压制警告,因为这样会忽略其他意外压制的警告。但是恕我直言,对于只影响一行代码的警告,不发出警告是完全合理的。

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

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

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

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

对于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

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

@Mock
Foo<Bar> barMock;

为什么不用间谍

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

所以你得到了这个:

Foo mockFoo = mock(Foo.class);

解决方法,从我最不喜欢的开始到最喜欢的:

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

(在我看来)最简单和最易读的方法是使用方法级注入。

这将导致在测试方法中拥有所有测试数据。这将使您的测试类保持干净,因为没有“浮动”mock。

@ExtendWith(MockitoExtension.class)
public class SomeClassTest {


@Test
void someTestMethod(@Mock Foo<Bar> fooMock) {
// do something with your mock
}
    

}