'Mockito Matcher for a TypeReference

I am trying to write a Java unit test using Mockito but am having trouble to get a matcher to work.

I want to test the following class

CustomService.java

public class CustomService {

private final ApplicationProperties props;

public CustomService(ApplicationProperties props){
    this.props = props;
}

private final ObjectMapper mapper = new ObjectMapper();
public void method(JsonNode message) throws CustomException {
    try {
        List<String> actions = mapper.readValue(message.get("actions").toString(), mapper.getTypeFactory().constructCollectionType(List.class, String.class));
        System.out.println(actions);
    } catch (IOException ex){
        throw new CustomException(ex);
    }
}
}

I have a CustomException class

CustomExcepton.java

@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public class CustomException extends Exception {

public CustomException(Throwable cause) {
    super(cause);
    }

}

I want to test that the CustomException is thrown. I am using Mockito. I have tried the below but the given statement doesn't seem to pick up the (readValue) line of code in the method

CustomServiceTest.java

public class CustomServiceTest {

private final ApplicationProperties props = mock(ApplicationProperties.class);
private final CustomService customService = new CustomService(props);
private static final ObjectMapper objectMapper = new ObjectMapper();

@Test
public void CustomExceptionIsThrown() throws Exception {
    ObjectMapper mapper = mock(ObjectMapper.class);
    given(mapper.readValue(anyString(), any(TypeReference.class))).willThrow(new IOException("This is a test"));
    String json = "{\"actions\":[\"ac1\",\"ac2\",\"ac3\",\"ac4\"]}";
    JsonNode d = objectMapper.readTree(json);
    assertThrows(CustomException.class, () ->
            customService.method(d));
    }
}

I get the following error when I run the test

Expected exception.CustomException to be thrown, but nothing was thrown..

Help would be appreciated.



Solution 1:[1]

There are still some issues with your example code.

  • You did not inject the Mock of ObjectMapper into your CustomService class.

  • As your class under test now has a args constructor, you won't be able to inject the mock into your test. You will need to adjust the constructor to pass the ObjectMapper from the outside or rely on Reflections to place the mock manually.

The general advice would be to pass the ObjectMapper as argument into the construtor. Doing it this way, you won't need to change the final on the field mapper.

private final ApplicationProperties props;
private final ObjectMapper mapper;

public CustomService(ApplicationProperties props, ObjectMapper mapper){
    this.props = props;
    this.mapper = mapper;
}
  • You still have not defined the behaviour for the mock when the getTypeFactory() method is invoked. You should get a NullPointerException at this point (if your code actually used that mock).
    In the example below I replaced this definition by using the RETURNS_DEEP_STUBS option, which causes mapper.getTypeFactory().constructCollectionType(List.class, String.class) to return a mock of CollectionType.

  • The mock definition with TypeReference is not required, as it does not match your method parameters which are used when the readValue method is invoked. Instead you will have to work with CollectionType.

  • As your method under test is void you will have to use the willThrow(...).given(...) syntax instead.


Here an example of a working test:
(Only works for a CustomService class with a no-args constructor)

@ExtendWith(MockitoExtension.class)
public class JacksonTest {

    static class CustomException extends Exception {
        public CustomException(IOException ex) {
            super(ex);
        }
    }

    static class CustomService { 
        private ObjectMapper mapper = new ObjectMapper();

        public void method(String c) throws CustomException {       
            try {
                mapper.readValue(c, mapper.getTypeFactory().constructCollectionType(List.class, String.class));            
            } catch (IOException ex){
                throw new CustomException(ex);
            }
        }
    }

    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
    ObjectMapper mapper;

    @InjectMocks
    CustomService customService;

    @Test
    public void testCustomExceptionIsThrown() throws Exception {

        BDDMockito.willThrow(new IOException("This is a test")).given(mapper).readValue(ArgumentMatchers.anyString(), ArgumentMatchers.any(CollectionType.class));
        Assertions.assertThrows(CustomException.class, () -> customService.method("x")); 
    }
}

Dependencies:

testCompile "org.junit.jupiter:junit-jupiter-api:5.5.2"
testCompile "org.junit.jupiter:junit-jupiter-engine:5.5.2"
testCompile "org.mockito:mockito-core:3.0.0"
testCompile "org.mockito:mockito-junit-jupiter:3.0.0"
compile "com.fasterxml.jackson.core:jackson-databind:2.9.9"

Solution 2:[2]

I also met this same problem, and i find that although i mock objectMapper like " when(objectMapper.readValue(anyString(), any(TypeReference.class))).thenReturn(xxxx);" but when i invoke the test, the first param of readValue(xx,xx) is null which should be a String object. So, you might want to check readValue method input param again.Hope it will help.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Haomiao