'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
ObjectMapperinto yourCustomServiceclass.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
ObjectMapperfrom the outside or rely onReflectionsto 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 aNullPointerExceptionat this point (if your code actually used that mock).
In the example below I replaced this definition by using theRETURNS_DEEP_STUBSoption, which causesmapper.getTypeFactory().constructCollectionType(List.class, String.class)to return a mock ofCollectionType.The mock definition with
TypeReferenceis not required, as it does not match your method parameters which are used when thereadValuemethod is invoked. Instead you will have to work withCollectionType.As your method under test is
voidyou will have to use thewillThrow(...).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 |
