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?
Advertisement
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 usinggetType
. - Create an
ArgumentMatcher
implementation that usesgetType
and consume it withargThat
. - Switch to
ArgumentMatchers.any()
orArgumentMatchers.<TypeReference<Map<String, Object>>>any()
, which was previously on theMatchers
andMockito
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?