Skip to content

Handle anonymous class with Generics in Mockito

I am trying to write unit test using Powermockito for following method –

public String getGenerator(String json) throws IOException {
    String jwt = "";
    ObjectMapper mapper = new ObjectMapper();

    // convert JSON string to Map
    Map<String, Object> map = mapper.readValue(json, new TypeReference<Map<String, Object>>() {
    }); // Here passing TypeReference annonymously

    // Create a JWT
    JWTGenerator generator = new JWTGenerator();
    if (map != null && map.size() > 0) {
        jwt = generator.createJWT(map);
    }

    return jwt;
}

I have written test method as –

@Test
public void testGetJWTGenerator() throws Exception {
    ObjectMapper mockMapper = PowerMockito.mock(ObjectMapper.class);
    PowerMockito.whenNew(ObjectMapper.class).withNoArguments().thenReturn(mockMapper);

    JWTGenerator mockJWTDecoder = PowerMockito.mock(JWTGenerator.class);
    PowerMockito.whenNew(JWTGenerator.class).withNoArguments().thenReturn(mockJWTDecoder);

    Map<String, Object> anyMap = new HashMap<String, Object>();
    anyMap.put("testStr", new Object());

    TypeReference<Map<String, Object>> mockTypeReference = (TypeReference<Map<String, Object>>) PowerMockito.mock(TypeReference.class);
    PowerMockito.whenNew(TypeReference.class).withNoArguments().thenReturn(mockTypeReference);

    PowerMockito.when(mockMapper.readValue(Mockito.anyString(), Mockito.eq(mockTypeReference))).thenReturn(anyMap);
    PowerMockito.when(mockJWTDecoder.createJWT(anyMap)).thenReturn(Mockito.anyString());
    utilityController = new UtilityController();
    utilityController.getJWTGenerator("{"someStr":"someStr"}");
    Mockito.verify(mockJWTDecoder, Mockito.times(1)).createJWT(anyMap);
}

When I run this test I always get it failed saying –

Wanted but not invoked:
jWTGenerator.createJWT(
    {[email protected]}
);

It looks like stub is not working as I always get “null” for this line –

Map<String, Object> map = mapper.readValue(json, new TypeReference<Map<String, Object>>() {
        }); // Here passing TypeReference annonymously

Is it because anonymous instantiation of TypeReference class?

Answer

Yes, it’s because of the anonymous inner class. Specifically, you tell PowerMockito to replace the call to new TypeReference:

PowerMockito.whenNew(TypeReference.class).withNoArguments()
    .thenReturn(mockTypeReference);

But what you’re actually creating is an anonymous class that extends TypeReference, not a TypeReference itself:

Map<String, Object> map = mapper.readValue(
    json, new TypeReference<Map<String, Object>>() {});

This is going to be especially tricky for you. My normal advice here is “don’t mock data objects” like TypeReference, because it’s a no-dependency token/value/data object that works heavily on reflection, but it also doesn’t support equals the way its cousins in Guice and Guava do; unlike with Guice and Guava you couldn’t just create your own real TypeReference in your test and match with Mockito’s eq.

You still shouldn’t mock TypeReference, but you’ll also need to adjust how you assert against it:

  • Extract the anonymous TypeReference subclass to a named equivalent, if Jackson lets you, and then use isA to check its type.
  • Extract the TypeReference to a visible constant and check reference equality on it.
  • Use a Captor and check the TypeReference’s generic type using getType.
  • Create an ArgumentMatcher implementation that uses getType and consume it with argThat.
  • Switch to ArgumentMatchers.any() or ArgumentMatchers.<TypeReference<Map<String, Object>>>any(), which was previously on the Matchers and Mockito interfaces. The value is unlikely to change anyway, so pragmatically your system and test may be more readable and robust from disregarding the check than from convincing PowerMock otherwise.
  • Ideally, use real dependencies wherever you can and just check that the function works, not that you’re interacting with the right collaborators in the ways that your implementation happens to. A working function is what you’re after anyway, right?