Issue
I'm having a hard time mocking a com.fasterxml.jackson.databind.ObjectMapper
.
In particular this the class I want to test:
@Service
public class ClassToBeTested {
private final ObjectMapper mapper;
public ClassToBeTested(ObjectMapper mapper) {
this.mapper = mapper;
}
public void publicMethod(messageDto dto) throws JsonProcessingException {
try{
privateMethod(dto, param, "other string");
} catch(JsonProcessingException e){
// do stuff
}
}
private void privateMethod(Object dto, String param, String param2) {
Map<String, Object> attributes = new HashMap<>();
attributes.putAll(mapper.readValue(mapper.writeValueAsString(dto),
new TypeReference<Map<String, Object>>() {
}));
attributes.put("level", "error");
//other stuff
}
}
My test class:
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = TestContextConfiguration.class)
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
public class ClassToBeTestedTest {
@MockBean
private ObjectMapper mapper;
private ClassToBeTested classToBeTestedActor;
@PostConstruct
public void initMock() throws JsonProcessingException {
when(mapper.writeValueAsString(any())).thenReturn(any());
when(mapper.readValue(anyString(), Mockito.<TypeReference<Map<String,
Object>>>any())).thenReturn(new HashMap<>());
}
@Before
public void setup() {
classToBeTestedActor = new ClassToBeTested(mapper);
}
@Test
public void shouldFailJsonParsing() throws JsonProcessingException {
// Given
final Dto dto = mock(Dto.class);
// When
when(mapper.writeValueAsString(any()))
.thenThrow(JsonProcessingException.class);
// Then
Assertions.assertThrows(JsonProcessingException.class,
() -> classToBeTestedActor.publicMethod(dto));
}
}
The purpose is to trigger and test the JsonProcessingException
. The test fails because of an InvalidUseOfMatchersException
:
Invalid use of argument matchers!
2 matchers expected, 3 recorded:
-> at com.ocado.crm.service.ClassToBeTestedTest.initMock(ClassToBeTestedTest.java:47)
-> at com.ocado.crm.service.ClassToBeTestedTest.initMock(ClassToBeTestedTest.java:48)
-> at com.ocado.crm.service.ClassToBeTestedTest.initMock(ClassToBeTestedTest.java:48)
where line 46 and 47 respectively are:
when(mapper.writeValueAsString(any())).thenReturn(any());
when(mapper.readValue(anyString(), Mockito.<TypeReference<Map<String, Object>>>any()))
.thenReturn(new HashMap<>());
which makes no sense to me since I think I didn't mixed matchers and raw values.
How can I mock the ObjectMapper
correctly so that I can test JsonProcessingException
?
Solution
I don't see the reason behind writing a test which mocks Object Mapper to throw some exception and then test whether the exception was thrown or not. Instead the test should be for the custom logic which is developed by you. If you have a method like this -
public void publicMethod(messageDto dto) {
try{
privateMethod(dto, param, "other string");
} catch(JsonProcessingException e){
// do stuff
dto.setXXX("XXX"); // The test should be testing this
}
}
So, the test should be testing whether the dto is set correctly.
@Test
public void shouldFailJsonParsing() throws JsonProcessingException {
// Given
final Dto dto = new Dto();
// When
when(mapper.writeValueAsString(any()))
.thenThrow(JsonProcessingException.class);
// Invoke the method to be tested
classToBeTestedActor.publicMethod(dto);
// Test whether expected mock methods are invoked
verify(mapper, times(1)).writeValueAsString(any());
verifyNoMoreInteractions(mapper);
// Assert results
Assert.assertEquals("XXX", dto.getXXX());
}
I get your point and I kind of agree with that. I just want to understand why, even if I am forcing the mapper to throw that exception, I am unable to assert it
I think the reason for this is that the exception is catched by you. That is the reason, that exception isn't propagated to your test.
public void publicMethod(messageDto dto) throws JsonProcessingException {
try{
privateMethod(dto, param, "other string");
} catch(JsonProcessingException e){
// do stuff
}
}
Try changing this to -
public void publicMethod(messageDto dto) throws JsonProcessingException {
privateMethod(dto, param, "other string");
}
Answered By - SKumar