Java – Should I mock ObjectMapper in the unit tests

javamockingtestingunit testing

I have different services in a spring application that have a dependency on Jackson ObjectMapper, the unit tests rely on @InjectMocks to inject all the various dependencies to the class that is under test (including ObjectMapper).

Is this an acceptable behavior? Is there any accepted best practice about it? Should I continue to mock it and assume that it always returns what I expect, or should I call its real methods and test the output?

An additional clarification, we actually use ObjectMapper to write objects as strings and store them in String fields that will then be stored in a relational DB. For example,

@Entity
public class EntityThing {
    //...
    
    @Column
    private String json;

So, each time we need to store them, we convert the object (made up of many complex fields) to this json string and store it in the db; each time we need to retrieve it, we convert it back to the complex object it was before. Something like:

@Service
public class ThisIsAService {
   private final ObjectMapper mapper;

   public ThisIsAService(final ObjectMapper objectMapper){
       this.mapper = objectMapper;
   }

   public DomainObject fromEntity(EntityThing entity){
       ComplexObject obj = mapper.readValue(json, ComplexObject.class);

       return DomainObject.builder()
                           // ...
                           .oneOfManyFields(obj.getDate())
                           .anotherOfManyFields(obj.getSomethingElse())
                           .build();  
   }

   public EntityThing toEntity(ComplexObject obj){
       return EntityThing.builder()
                          // ...
                          .json(mapper.writeValueAsString(obj)
                          .build();
   }
   }
}

Best Answer

Never mock things you have no control over. It is fine to mock IO calls because of speed or because of violating independence of tests (this goes for databases as they are shared state). It is fine to use ObjectMapper directly in a service (no matter if it makes the onion boys cry, myself included), but do not mock it, because even if it is a unit test, you want to make sure that the code you do not control, does what you expect it to do.

Edit: I see that the answer was not clear enough, sorry for that.

When I talk about unit tests I talk about the black box tests on the core of the application (in the ports and adapters). I do not care how much classes I need to setup for the test of a certain business rule, I will do it. I will however create the fakes for the ports as I know that I have integration tests for them.

What I mean by integration tests is the test of the adapter. I do not care if it is a test for the password hashing library or a mapping library. Any library lives outside my core domain logic and my domain logic accesses that library through an interface.

Also, performance of the mocked code is not that fast. With mocked code I ran 16 basicly CRUD tests in 0.8 seconds, which caught me off guard. I thought that the problem with performance came from throwing exceptions in the tests, so I refactored the code to not throw any exceptions. The performance was the same. I then removed the mocking framework and then the tests ran in <0.1 seconds. With all the fakes I can run 70 tests in 0.2 seconds.

Another reason I do not like mocks is that they assume things that might change. Lets say you have Provider and Consumer classes. In Consumer class tests you mock the Provider responses. All is good, because you have the tests for the provider in place and it behaves as you expect. What happens when you have to change the behavior of the Provider? You might say that you change the tests of the Provider, but that is not true. The tests for the Consumer class are now lies, because the responses you expect from the provider are not true anymore, so you have to change the mocks as well. What happens when you have 5 or 6 Consumers that depend on the Provider? Let's say 10 tests for each Consumer and Provider. That means that you have to update 70 tests even if the behavior of the Consumers did not change.

The OP did not hide the library behind an interface. He does not control it. The lib in question is also not slow or so slow that it would need to be mocked for performance, so I don't think it should be mocked. It could be replaced with a custom mapper later. When testing the OP's methods I don't think that the call to the mapper matters, what I care about is that the result of the method is correct.

Edit 2: Here is a good talk about this topic https://www.youtube.com/watch?v=EZ05e7EMOLM

Related Topic