11

I am having a problem with mocking a method with mockito that looks like the following:

Map<Foo, ? extends Collection<Bar>> getValue();

The following is how I am using it in the test:

model = Mockito.mock(Model.class);
Map<Foo, List<Bar>> value = new HashMap<Foo, List<Bar>>();
Mockito.when(model.getValue()).thenReturn(value);

It gives the following error:

error: no suitable method found for thenReturn(Map<Foo,List<Bar>>)

Paul Bellora
  • 54,340
  • 18
  • 130
  • 181
Malik Firose
  • 389
  • 1
  • 5
  • 14

3 Answers3

17

You may use the following:

model = Mockito.mock(Model.class);
final Map<Foo, List<Bar>> value = new HashMap<Foo, List<Bar>>();

Mockito.when(model.getValue()).thenAnswer(new Answer<Map<Foo, List<Bar>>> () {
  public Map<Foo, List<Bar>> answer(InvocationOnMock invocation) throws Throwable {
    return value;
  }
});

Above can be shortened using lambda as:

Mockito.when(model.getValue()).thenAnswer(invocationOnMock -> value)
Martin Frank
  • 3,445
  • 1
  • 27
  • 47
Stefan Birkner
  • 24,059
  • 12
  • 57
  • 72
5

This error is happening because the compiler can't guarantee that the value type of the map returned by getValue is in fact List<Bar>. The type Map<Foo, ? extends Collection> means "a Map of Foos to some unknown type implementing Collection".

This is a good example of why using wildcards in return types is discouraged, because they generally inhibit the caller by obscuring the generic type information about what's returned (conversely, using wildcards in method parameters is encouraged because it makes things easier for the caller). I would recommend getting rid of the wildcard if possible:

Map<Foo, Collection<Bar>> getValue();

And use:

model = Mockito.mock(Model.class);
Map<Foo, Collection<Bar>> value = new HashMap<Foo, Collection<Bar>>();
Mockito.when(model.getValue()).thenReturn(value);

If you're unable to change the method's return type, you could use a "capture helper" method for the test:

private <T extends Collection<Bar>> test(Map<Foo, T> actual) {
    Map<Foo, T> expected = new HashMap<Foo, T>();
    Mockito.when(actual).thenReturn(expected);
}

...

model = Mockito.mock(Model.class);
test(model.getValue()); // T is resolved to wildcard capture

Of course this is very limiting because you can only test for an empty map without knowing what T is.

Paul Bellora
  • 54,340
  • 18
  • 130
  • 181
  • Even if the accepted solution is more concise, this one explains the "WHY" of the problem, which is the first question that experienced developers do to themselves. – camelCase Jun 15 '18 at 15:25
1

If you don't want to write helper functions, using doReturn...when works too, like this (although it's not type-safe):

Mockito.doReturn(value).when(model).getValue();
public class Test {
    interface Model {
        Map<Foo, ? extends Collection<Bar>> getValue();
    }
    class Bar {}
    class Foo {}

    public static void main(String[] args) {
        Model model = Mockito.mock(Model.class);
        Map<Foo, List<Bar>> value = new HashMap<Foo, List<Bar>>();

//      when(model.getValue()).thenReturn(value); // won't compile
        doReturn(value).when(model).getValue();

        System.out.println(model.getValue());
    }
}
seanf
  • 6,504
  • 3
  • 42
  • 52
  • This is working, but there was a difference in this implementation, right? The original calls the method anyway, this suggested way calls the mocked method only if really called in test or was it the other way around? – jan Nov 15 '18 at 08:35