6

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(
    {testStr=java.lang.Object@24bdb479}
);

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?

Saurabh
  • 2,384
  • 6
  • 37
  • 52

1 Answers1

9

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?
Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
  • 1
    Thanks for detailed explanation. I switched to Mockito.>> any() and it works. – Saurabh Dec 01 '16 at 08:05
  • @Jeff bowman, I am also facing the same issue. Can you explain more or provide an example? – Nimesh Apr 05 '18 at 14:04
  • @Nimesh I can answer specific questions, but without knowing an area of focus or your constraints, I'm not sure what to write that I haven't already written. And as for examples, there are enough techniques I listed and enough ways this might be applicable that I'm not sure it makes sense to pre-emptively write a specific example for each. – Jeff Bowman Apr 05 '18 at 14:45