Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mockito mocking when() for two generic methods of different types

Tags:

I have two generic objects of class Generic<T, R>:

Generic<TypeA, TypeB> genericOne;
Generic<TypeB, TypeC> genericTwo;

I have to mock:

when(someObject.someMethod(Matchers.<Generic<TypeA, TypeB>>any()))
                .thenReturn(responseOne());

when(someObject.someMethod(Matchers.<Generic<TypeB, TypeC>>any()))
                .thenReturn(responseTwo());

The problem is that mockito will not the see difference between this two method calls because of type erasure - it's both recognized as Generic<T, R> class.

Is there any way to to distinguish between this two method calls?

like image 799
RichardK Avatar asked Mar 16 '18 08:03

RichardK


2 Answers

Mockito doesn't know which generics was specified in front of any() invocation and anyway it doesn't matter.

The parameters expected in the recording a mock behavior has to rely on :

  • value (in terms of equals()
  • or in terms of captor if equals() is not adequate
  • or any() if the value of the parameter in the mock invocation doesn't matter or is not known in the test fixture

These never have to rely on the generic specified on.

To have a good example, look at Mockito.anyCollectionOf(Class<T> clazz) or Mockito.anyMapOf(Class<K> keyClazz, Class<V> valueClazz).

These methods know the class passed but anyway, their specifications state :

This method don't do any type checks, it is only there to avoid casting in your code. This might however change (type checks could be added) in a future major release.

Generally what you want to check is that the method to mock was invoked with the expected parameter and not any parameter : so any() will never achieve it.
So trying to check only the generic type but accepting any values as parameters seems be an anti pattern mocking.

In your case, if equals() is not convenient for the matching in the when() recording, use Mockito captor with verify() and do the check after the execution of the mock.

like image 172
davidxxx Avatar answered Oct 11 '22 18:10

davidxxx


I don't think it's possible to differentiate between the two as generic types are arased. But there are other options.

You can couple this calls to specific order so that even if mockito cannot differentiate between them it return expected value. This is done by chaining the thenReturn methods. First call returns responseOne() and second call returns responseTwo().

when(someObject.someMethod(Matchers.<Generic<TypeA, TypeB>>any()))
    .thenReturn(responseOne())
    .thenReturn(responseTwo());

But this is not optimal as this test will break if your implementations changes. So here comes the second option.

You can implement custom Fake for this object. So that you can bette control how this instance behaves. For example.

class SomeObjectFake {

    private final SomeResponse response;

    public SomeObject(SomeResponse response) {
        this.response = response;
    }

    public SomeResponse someMethod(Generic<TypeA, TypeB> arg) {
        // Decide what to return
        return response;
    }
}
like image 30
Januson Avatar answered Oct 11 '22 16:10

Januson