Testing Mocks
One common mistake engineers make is to test mocks. However, would you know what the right principles are and how to spot good or bad tests according to these principles?
IF you don't have first principles, you are very likely shooting into the dark. In Brazil, we have this metaphor that if you play chess with a pigeon (I will use a chicken in this post), you will very likely lose because the pigeon does not know what it is doing and probably will be upset with you for whatever you say.
People say if you truly know, you know how to explain. Well, I will go further and say IF you truly know you extract first principles and explain with those principles, so your explanation is short, concise, and direct. Otherwise, you are a chicken on a chessboard just doing random things you don't fully understand.
Side note on Performance: One of the root causes of engineering productivity issues lies here. Because engineers don't understand how things work from first principles and lack good mental models, they resort to trial-and-error approaches, which result in time drag and lead to delivery nightmares, where something that would take 1 day takes 2 weeks.
Why are we mocking after all?
So you want to do unit tests. Why? Because unit tests are cheap. They allow us to have quick feedback. We still want to do integration tests, but integration tests are order of magnitude more complex, take longer to run, require infrastructure for data induction, and by nature, you won't run them as often as unit tests.
Your goal is to apply testing, which means you are validating if your code is correct and if it is safe to go to production. Of course, we will apply other forms of testing, but the Unit test must be fast. The reason you use mocks if that if you dont use mocks, you will call the real database and downstream dependency services. Which will be slow and, by nature, will turn into Integration Tests. So you want speed. But are mocks the only way to get this speed? NO. Test doubles are a perfect option as well.
Let's recap: we want the following objectives:
- Have confidence via Unit Tests
- Have Fast Feedback == Speed matters
- Unit Test must be != Integration Tests
So to be 100% clear, the goal is not to MOCK. Mock is a way to achieve the objectives I just described.
Code Example
Consider the code (RentalService), pseudo-code in Scala 3x.
First, what should you test? What should you not test? Unit tests are PAIRs or classes. So if you have RentalService, you should have a RentalServiceTest file. If you have a HotelService, you should have a HotelServiceTest and apply unit tests to that class.
Good Principles of Unit Testing Mocks
- Test a Service: Don't test the controller or the dao (not at the unit test level).
- Mock your Downstream Dependencies: If you are testing a service, you mock the DAO or the Repository, meaning the classes that are called by the service.
- Make sure you cover all branches: Happy path is the basic. Testing the unhappy path is not enough; you should test all branches of the code. Now, do you know what a branch is? Do you know how to identify what to test and what not to test?
Code branches are execution flow paths. How many branches does this code have? If you can't identify the branches, you can't think about basic levels of testing.
What tests should be created here? (at minimum, yes we can do way more than this list)
- test_a_less_than_zero
- test_b_less_than_zero
- test_happy_path_both_positive
- test_edge_case_one_zero
- test_edge_case_both_zero
- test_edge_case_both_negative
Strongly typed languages have advantages because they do not allow us to pass arbitrary parameters. Languages like JavaScript require us to write much more testing. Or even typed languages like TypeScript, if I use `Any` for everything, the same issues arise.
Common Mistakes Leading to Testing Mocks
- You are mocking yourself: You must mock your downstream dependencies; you should not be mocking yourself. Meaning if you wrote the class HotelService, you should not be mocking hotel service, otherwise you are testing if 1 == 1. Meaning you are testing the mocks, and it is pointless.
- Sharing Mocks: Let's say you are testing three methods, and they are very similar and use the same dependencies. You will be tempted to create a shared mock and share it with all of them. The issue here is that, depending on what happens in the future, you might need to change the mocks for one situation, and the other 2 use cases might stop working. So it's better to duplicate code and have the mocks (isolation matters even with mocks).
- Implementation Coupling: Mocks should not test the implementation to the point that they are super-coupled with the underlying implementation. You should focus on the contract, not how the implementation works, because mocks must be resistant to refactoring. You want to test behavior, not implementation. This means you focus on what the code (via the contract) should do, not how it does it. This is an essential property because if you refactor and your mocks break everything, false negatives can undermine trust in tests, and thats very bad.
We also need to remember to not mock everything. Sometimes it is much easier to just create an object (Test Doubles) and pass it by parameter (when possible). If you have proper dependency injection and proper OOP in place, it is possible to get away without using mocks in several scenarios.
A note about process
Often, teams conduct code reviews, where discussions may arise depending on the quality of the reviews. However, do you team review the "invisible things"? Meaning how people behave when you are not watching or not even in the room. How people operate is what really needs to be reviewed and discussed because it leads you better ways of working and true learning and education.
Cheers,
Diego Pacheco



