0

I am using mockito to test a generic method. But i get a ClassCastException when i run the junit-test.

The method under test looks like this:

public ExampleClass {
    public <E> E randomObject(List<E> list) {
            E e = list.get(0);
            return e;
    }
}

The mock looks like this:

ExampleClass exampleMock = mock(ExampleClass.class);
List listMock = mock(List.class);
when(exampleMock.randomObject(Matchers<List<String>any())).thenReturn("Hello");
when(exampleMock.randomObject(Matchers.List<Integer>any())).thenReturn(20);

The Exception appears at the second definition of when-method. It looks like the method accpets only one type which isn't changeable. But why is that so? When i use the generic method with two different types in plain java the Exception won't appear.

Can someone please help?

  • Are you mocking ExampleClass while also testing it? Your system under test should always be real code, not a mock, though you could mock out its collaborators. (Mocking out a list is often an antipattern as well, though I'd understand needing that to overcome the randomness. Often you can just pass in a real List implementation instead.) – Jeff Bowman Mar 02 '15 at 05:49

1 Answers1

2

tl;dr

Split the code in multiple test methods. Or chain the thenReturn API.

long story

As you are probably aware Java generics where implemented with type erasure, that means that most of the generics you see in the code are only present in the source code, not in the compiled bytecode.

E.g. the following signature here

<E> E randomObject(List<E> list)

is compiled to

Object randomObject(List list)

That's the signature that mockito sees. That goes the same way for matchers :

when(exampleMock.randomObject(Matchers.<List<String>>any())).thenReturn("Hello");
when(exampleMock.randomObject(Matchers.<List<Integer>>any())).thenReturn(20);

becomes

when(exampleMock.randomObject(Matchers.any())).thenReturn("Hello");
when(exampleMock.randomObject(Matchers.any())).thenReturn(20);

Mockito find it's the same call with the same matcher. While it the code it reads like two different stub, mockito doesn't know that and can only assume the developer want to override this first stub.

This mockito behavior is needed for scenarios where a default stub is declared in some @Before method and that stub need to be overriden in some @Test method.

In this case the code should either be split, or if part of a more complex scenario, the stub should use the chain API, e.g.

when(exampleMock.randomObject(Matchers.anyList()))
       .thenReturn("Hello")
       .thenReturn(20);

_Note that the matcher has changed to anyList() which may be more compile time friendly.

The test could be annotated with @SuppressWarnings("unchecked").

bric3
  • 40,072
  • 9
  • 91
  • 111
  • Thank you for the explanation, but the use of the chain API doesnt work. I get a compiler Error which says that "The method randomObject(List) in the type RandomBasics is not applicable for the arguments (Object)" – user3637636 Mar 02 '15 at 00:11
  • I was writing on an iPad, but I just tested the code, with java, it compiles and works flawlessly. But compilers have changed over Java version, and if using the Eclipse compiler, it may have even more issues. I think you want to use this matcher instead `Matchers.anyList()`. (I updated the answer accordingly) – bric3 Mar 02 '15 at 16:42