Is there a clean method of mocking a class with generic parameters? Say I have to mock a class Foo<T>
which I need to pass into a method that expects a Foo<Bar>
. I can do the following easily enough:
Foo mockFoo = mock(Foo.class);
when(mockFoo.getValue).thenReturn(new Bar());
Assuming getValue()
returns the generic type T
. But that's going to have kittens when I later pass it into a method expecting Foo<Bar>
. Is casting the only means of doing this?
You could always create an intermediate class/interface that would satisfy the generic type that you are wanting to specify. For example, if Foo was an interface, you could create the following interface in your test class.
private interface FooBar extends Foo<Bar>
{
}
In situations where Foo is a non-final class, you could just extend the class with the following code and do the same thing:
public class FooBar extends Foo<Bar>
{
}
Then you could consume either of the above examples with the following code:
Foo<Bar> mockFoo = mock(FooBar.class);
when(mockFoo.getValue()).thenReturn(new Bar());
With JUnit5 I think the best way is to @ExtendWith(MockitoExtension.class) with @Mock in the method parameter or the field.
The following example demonstrates that with Hamcrest matchers.
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"));
}
}
See https://www.vogella.com/tutorials/Mockito/article.html for the required Maven/Gradle dependencies.
Create a test utility method. Specially useful if you need it for more than once.
@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);
}
As the other answers mentioned, there's not a great way to use the mock()
& spy()
methods directly without unsafe generics access and/or suppressing generics warnings.
There is currently an open issue in the Mockito project (#1531) to add support for using the mock()
& spy()
methods without generics warnings. The issue was opened in November 2018, but there aren't any indications whether it will be prioritized.
One other way around this is to use @Mock
annotation instead.
Doesn't work in all cases, but looks much sexier :)
Here's an example:
@RunWith(MockitoJUnitRunner.class)
public class FooTests {
@Mock
public Foo<Bar> fooMock;
@Test
public void testFoo() {
when(fooMock.getValue()).thenReturn(new Bar());
}
}
The MockitoJUnitRunner
initializes the fields annotated with @Mock
.
I agree that one shouldn't suppress warnings in classes or methods as one could overlook other, accidentally suppressed warnings. But IMHO it's absolutely reasonable to suppress a warning that affects only a single line of code.
@SuppressWarnings("unchecked")
Foo<Bar> mockFoo = mock(Foo.class);
Here is an interesting case: method receieves generic collection and returns generic collection of same base type. For example:
Collection<? extends Assertion> map(Collection<? extends Assertion> assertions);
This method can be mocked with combination of Mockito anyCollectionOf matcher and the 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>();
}
});
Source: Stackoverflow.com