Search code examples
javamockitojunit5

JUnit - How to mock local variable object method call


The code for my usecase is below:

My class:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class MyClass {
    public String getString() {
        // object need mocking
        ObjectMapper mapper = new ObjectMapper()
        try {
                          // method need mocking
            return mapper.writeValueAsString(List.of("1", "2"));
        }
        catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }
}

Test code:

@Test
public void test_get_string() throws Exception {
    ObjectMapper mapper = Mockito.mock(ObjectMapper.class);
    Mockito.when(mapper.writeValueAsString(Mockito.anyList()))
          .thenThrow(JsonProcessingException.class);
    
    Assertions.assertThrows(RuntimeException.class, () -> {
        new MyClass().getString();
    });
    // -> failed
    // Expected RuntimeException but nothing was thrown
}

I want to mock the ObjectMapper and make it throw JsonProcessingException on writeValueAsString method call, but the test keeps failing as if it doesn't use my mocked mapper at all. But if I make the mapper a property of MyClass and mock it, then the test passes.

public class MyClass {
    // make mapper a property
    private ObjectMapper mapper;
    
    public MyClass(ObjectMapper mapper) {
        this.mapper = mapper;
    }

    public String getString() {
        try {
            return mapper.writeValueAsString(List.of("1", "2"));
        }
        catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }
}
@Test
public void test_get_string() throws Exception {
    ObjectMapper mapper = Mockito.mock(ObjectMapper.class);
    Mockito.when(mapper.writeValueAsString(Mockito.anyList()))
          .thenThrow(JsonProcessingException.class);
    
    Assertions.assertThrows(RuntimeException.class, () -> {
        // pass mapper on creating object
        new MyClass(mapper).getString();
    }); // -> success
}

In case I don't want to make mapper a property of my class, how should I properly mock it ?


Solution

  • You can't mock a local variable declared in a method using Mockito. You can make ObjectMapper a property of your class MyClass. But if you don't want to do that, the other option is to add a protected method in your MyClass that returns an instance of ObjectMapper

    public class MyClass {
        public String getString() {
            // object need mocking
            ObjectMapper mapper = createObjectMapper()
            try {
                              // method need mocking
                return mapper.writeValueAsString(List.of("1", "2"));
            }
            catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
        }
       protected ObjectMapper createObjectMapper() {
           return new ObjectMapper();
       }
    }
    

    Then in your test case, you can spy your class and stub this new method to return a mocked ObjectMapper as shown below:

    @Test
    public void test_get_string() throws Exception {
        ObjectMapper mapper = Mockito.mock(ObjectMapper.class);
        Mockito.when(mapper.writeValueAsString(Mockito.anyList()))
              .thenThrow(JsonProcessingException.class);
        MyClass myClass = Mockito.spy(new MyClass ());
        doReturn(mapper).when(myClass).createObjectMapper();
        Assertions.assertThrows(RuntimeException.class, () -> {
            myClass.getString();
        });
    }