Using Mockito to mock classes with generic parameters

223,266

Solution 1

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());

Solution 2

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.

Solution 3

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());

Solution 4

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

Solution 5

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);
Share:
223,266
Tim Clemons
Author by

Tim Clemons

Updated on July 08, 2022

Comments

  • Tim Clemons
    Tim Clemons almost 2 years

    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?

  • odwl
    odwl over 13 years
    Yes but you still have a warning. Is that possible to avoid the warning?
  • qualidafial
    qualidafial over 13 years
    @SuppressWarnings("unchecked")
  • Pure Function
    Pure Function about 10 years
    this is deprecated in 1.9.5. :( Seems much cleaner to me.
  • Tim Clemons
    Tim Clemons about 10 years
    Provided Foo is an interface or non-final class, this appears to be a reasonably elegant solution. Thanks.
  • dsingleton
    dsingleton about 10 years
    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!
  • Dee Choksi
    Dee Choksi almost 10 years
    @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)
  • Nicole
    Nicole almost 10 years
    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!
  • Rüdiger Schulz
    Rüdiger Schulz over 9 years
    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.
  • Magnilex
    Magnilex over 9 years
    I think this is fully acceptable since we are talking about a mock object in a unit test.
  • William Jarvis
    William Jarvis over 8 years
    Could extend your answer to make a general utility method passing in the class you want to mock.
  • borjab
    borjab about 8 years
    For initiation just call MockitoAnnotations.initMocks(this);
  • TWiStErRob
    TWiStErRob almost 8 years
    @WilliamDutton static <T> 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<P> mock = mock(G.class) case.
  • pablisco
    pablisco over 7 years
    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)
  • Brian McCutchon
    Brian McCutchon about 7 years
    @borjab Not necessary if you're using the MockitoJUnitRunner, as mentioned in the answer.
  • Oleksandr Tarasenko
    Oleksandr Tarasenko over 6 years
    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.
  • Krzysztof Krasoń
    Krzysztof Krasoń almost 6 years
    @demaniak It doesn't work at all. Argument matchers can't be used in that context.
  • Superole
    Superole almost 6 years
    @demaniak That will compile just fine, but when running the test it will throw InvalidUseOfMatchersException (which is a RuntimeException)
  • demaniak
    demaniak almost 6 years
    @KrzysztofKrasoń and others is correct - my comment was indeed invalid. The best plan seems to be the cast unfortunately.
  • Avery Michelle Dawn
    Avery Michelle Dawn over 5 years
    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.
  • xbakesx
    xbakesx over 3 years
    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.
  • Pyves
    Pyves almost 3 years
    This will still lead to warnings,as highlighted by other answers.
  • Pyves
    Pyves almost 3 years
    This solution is already detailed in several other answers, I'm unsure how it adds any value.
  • xilef
    xilef almost 3 years
    @Pyves this covers JUnit5 which does not work with @RunWith(MockitoJUnitRunner.class)
  • Pyves
    Pyves almost 3 years
    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.
  • JackHammer
    JackHammer over 2 years
    If you need to get rid of the warning, move the mock into class property level and annotate with @Mock