6

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();
   }
   }
}
9
  • 3
    JSON is like a peripheral to your application, ObjectMapper should only be present at boundaries of your system. You can test these boundaries (without mocking ObjectMapper) by passing them JSON input, and asserting that the POJO output is correct. From there inwards, your application only transacts in POJO model objects, and not JSON, so there's no need to mock ObjectMapper.
    – Alexander
    Commented Sep 29, 2020 at 16:05
  • 1
    Can you share some code to illustrate your case?
    – Laiv
    Commented Sep 29, 2020 at 16:06
  • @Laiv I have added some code to make the situation clearer Commented Sep 29, 2020 at 16:22
  • 7
    Don't think you need to mock ObjectMapper. It's an implementation detail, despite to be exposed as a dependency. Your test should rather focus on testing given this input, I get this output. How is that possible should be irrelevant for the testing code.
    – Laiv
    Commented Sep 29, 2020 at 21:35
  • 1
    Note that you can using JSON with Hibernate and some DBs is simple: github.com/vladmihalcea/hibernate-types Commented Sep 30, 2020 at 16:03

1 Answer 1

-1

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

2
  • What you do or don't have control over is a poor guide to mocking. Mocking is a way to isolate. Sure it doesn't test what was mocked but that doesn't prevent you from writing a slower end-to-end test that, unlike the fast mocked unit test, you don't run it every time you compile. Commented Jun 10, 2021 at 20:43
  • @candied_orange It is not. As I said - you can mock IO for speed, but that should be it if you are not hiding the library behind an interface and have integration tests with the library. I'll edit the answer to further elaborate on this.
    – Blaž Mrak
    Commented Jun 10, 2021 at 21:41

Not the answer you're looking for? Browse other questions tagged or ask your own question.