383

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?

Tim Clemons
  • 6,231
  • 4
  • 25
  • 23
  • 3
    Why use foo and bar over more meaningful names. Just created a whole load of confusion for a lot of people. – Kaigo Jul 25 '20 at 20:26
  • 9
    @Kaigo it's quite typical in programming examples to use foo, bar and baz, especially when anonymizing real-world examples to hide confidential details. now, I'm more concerned with the fact that OP used the phrase "that's going to have kittens"...literally never heard anyone say that before ;) – Adam Burley Mar 13 '21 at 14:04
  • Everyone knows that "having kittens" is bad thing for code to be doing, even if it's pretty great for cats. `:--)` – Charlie Reitzel Jan 29 '22 at 18:12

12 Answers12

363

I think you do need to cast it, but it shouldn't be too bad:

Foo<Bar> mockFoo = (Foo<Bar>) mock(Foo.class);
when(mockFoo.getValue()).thenReturn(new Bar());
osundblad
  • 2,675
  • 1
  • 29
  • 34
John Paulett
  • 15,596
  • 4
  • 45
  • 38
352

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.

Marek Kirejczyk
  • 5,364
  • 3
  • 17
  • 10
  • 3
    this is deprecated in 1.9.5. :( Seems much cleaner to me. – Pure Function Mar 27 '14 at 21:36
  • 17
    @CodeNovitiate I couldn't find any deprecation annotations on MockitoJUnitRunner and Mock in 1.9.5. So, what is deprecated? (Yes, org.mockito.MockitoAnnotations.Mock is deprecated, but you should use org.mockito.Mock instead) – neu242 May 22 '14 at 07:31
  • 16
    Well done, this worked perfectly for me. It's not just "sexier", it avoids a warning without using `SuppressWarnings`. Warnings exist for a reason, it's better to not be in the habit of suppressing them. Thanks! – Nicole Jun 03 '14 at 17:43
  • 4
    There is one thing I don't like about using `@Mock` instead of `mock()`: the fields are still null during construction time, so I cannot insert dependencies at that time and cannot make the fields final. The former can be solved by a `@Before`-annotated method of course. – Rüdiger Schulz Sep 25 '14 at 09:26
  • 4
    For initiation just call MockitoAnnotations.initMocks(this); – borjab Apr 06 '16 at 12:31
  • 1
    2 problems with this: 1. If the variable is not used the IDE won't warm you. 2. Using the mockito annotations has a significant impact on test time (around 100-300ms for a medium size suite) – pablisco Dec 14 '16 at 14:51
  • 1
    @borjab Not necessary if you're using the `MockitoJUnitRunner`, as mentioned in the answer. – Brian McCutchon Feb 25 '17 at 18:12
  • Also you can use initMocks(this) in `@Before` method instead of using `@RunWith(MockitoJUnitRunner.class)`. It helps if you want to use other runner class in `@RunWith` annotation (like `@RunWith(Parameterized.class)`. I usually prefer this approach. – Oleksandr Tarasenko Feb 05 '18 at 12:08
  • 2
    FYI: Mockito is just doing the cast and suppressing the warning behind the scenes. I also prefer this, but, functionally, it's not any better. I use `@Mock` + `MockitoAnnotations.initMocks(this)` when I know all the mocks I need statically. I use `mock(Class.class)` + casting when I need to generate mocks dynamically/on-the-fly. – Avery Michelle Dawn Feb 07 '19 at 21:00
57

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());
Konrad Borowski
  • 11,584
  • 3
  • 57
  • 71
dsingleton
  • 976
  • 6
  • 7
  • 4
    Provided `Foo` is an interface or non-final class, this appears to be a reasonably elegant solution. Thanks. – Tim Clemons Apr 29 '14 at 15:39
  • I updated the answer to include examples for non-final classes as well. Ideally you would be coding against an interface, but that's not always going to be the case. Good catch! – dsingleton Apr 30 '14 at 15:15
23

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);
}
acdcjunior
  • 132,397
  • 37
  • 331
  • 304
  • Could extend your answer to make a general utility method passing in the class you want to mock. – William Jarvis Feb 05 '16 at 10:19
  • 2
    @WilliamDutton `static T genericMock(Class super T> classToMock) { return (T)mock(classToMock); }` it doesn't even need a single suppression :) But be careful, `Integer num = genericMock(Number.class)` compiles, but throws `ClassCastException`. This is only useful for the most common `G

    mock = mock(G.class)` case.

    – TWiStErRob Jul 13 '16 at 12:06
19

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);
Tobias Uhmann
  • 2,757
  • 2
  • 25
  • 35
  • Sorry to necro this post, but I've never seen annotations on a statement like this in Java, and [the docs](https://docs.oracle.com/javase/tutorial/java/annotations/basics.html) don't say anything about it either. Am I missing something? – Rabadash8820 Aug 12 '22 at 16:30
9

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 that it will be prioritized. Per one of the Mockito contributor's comments on the issue:

Given that .class does not play well with generics, I don't think there is any solution we can do in Mockito. You can already do @Mock (the JUnit5 extension also allows method parameter @Mocks) and that should be a suitable alternative. Therefore, we can keep this issue open, but it is unlikely ever to be fixed, given that @Mock is a better API.

M. Justin
  • 14,487
  • 7
  • 91
  • 130
  • 1
    For some cases `spy()` is alright, because you can pass an instance to it. Obviously doesn't cover all your bases but if you can instantiate a generic instance you're good to go. – xbakesx Sep 28 '20 at 22:01
9

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.

vogella
  • 24,574
  • 4
  • 29
  • 26
4

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>();
         }
     });
qza
  • 599
  • 4
  • 7
4

JUnit5: use @ExtendWith(MockitoExtension.class) on the test class then add this field:

@Mock
Foo<Bar> barMock;
xilef
  • 2,199
  • 22
  • 16
  • This solution is already detailed in several other answers, I'm unsure how it adds any value. – Pyves Jun 07 '21 at 08:33
  • @Pyves this covers JUnit5 which does not work with `@RunWith(MockitoJUnitRunner.class)` – xilef Jun 09 '21 at 08:09
  • I am seeing at least one other answer that already covered `@ExtendWith(MockitoExtension.class)`, and other answers that work regardless of the version of JUnit in use. This isn't really the key point of the question/answer anyway. – Pyves Jun 09 '21 at 08:18
3

So you have this:

Foo mockFoo = mock(Foo.class);

Ways to fix it, starting from my least favourite to most:

  1. Use @SuppressWarnings("unchecked") annotation. Doesn't really fix it, but you'll stop getting the warnings.
@SuppressWarnings("unchecked")
Foo mockFoo = mock(Foo.class);
when(mockFoo.getValue).thenReturn(new Bar());
  1. Cast it. Though it still gives warnings, unfortunately. So you need to use the annotation here as well:
@SuppressWarnings("unchecked")
Foo<Bar> mockFoo = (Foo<Bar>) mock(Foo.class);
when(mockFoo.getValue).thenReturn(new Bar());
  1. Use @Mock annotation. There will be no warnings. Here, when can be added in actual tests.
@Mock
public Foo<Bar> fooMock;
  1. Use @MockBean annotation. This will create a mocked bean directly. No warnings.
@MockBean
public Foo<Bar> fooMock;
1

The (in my opinion) most easiest and most readable approach is to use method level injection.

This will result in having all test data within the test method. This will keep your test classes clean as there are no 'floating' mock's.

@ExtendWith(MockitoExtension.class)
public class SomeClassTest {

    @Test
    void someTestMethod(@Mock Foo<Bar> fooMock) {
        // do something with your mock
    }
    
}
MevlütÖzdemir
  • 3,180
  • 1
  • 23
  • 28
0

why not using spy

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