To mock or not to mock
January 11, 2021 - 783 words -
Found a typo? Edit me
How to escape the mocking hell
Mocking is useful, but “what to mock” usually turns out to be a bit more complicated than expected if you don’t treat this carefully.
How to escape the mocking hell
What is actually happening when we create a mock? Which types of mocks are there? Is mocking good or bad? Well, as always, everything depends on the context. And here we will consider some of the main situations about when to mock and when not to mock, but especially why.
What happens when you mock something?
First, we should define what is a mock:
In a unit test, mock objects can simulate the behavior of complex, real objects and are therefore useful when it is impractical or impossible to incorporate a real object into a unit test.
Mocking makes sense in a unit testing context. An integration test should go through the real implementation checking the integration between multiple units, which are even allowed to talk to the DB or File IO: infrastructure code. Therefore we should agree that a unit test is a fast and deterministic test that doesn’t rely on external dependencies and doesn’t require any special context to run.
Mock objects meet the interface requirements. In consequence, they allow us to write and unit-test functionality without calling complex underlying or collaborating classes.
A mock is a test double that stands in for real implementation code during the unit testing process. It is also capable of producing assertions about how it was manipulated by the test subject during the test run.
I strongly recommend you to read this post if you want to get into the details of why Mocking is a code smell (Topics like these: What is a mock? What is a unit test? What is test coverage? What is tight coupling? What causes tight coupling? What does composition have to do with mocking? How do we remove coupling? and more!)
The problem with mocking
When you mock you are overriding the logic of the mocked class. The real logic is getting hidden behind the scenes and there is actually where bugs love to live. Consider that:
The mock may have attributes, methods, or arguments that the real object doesn’t.
The mock’s return values may differ from the real objects’ return values. For example, it may return a different type of object that has different attributes.
The mock’s side effects and behavior may differ from the real objects’ ones. For example, maybe the mock fails to raise an exception when the real object would raise it.
Alternatives to mocking
“Are you saying that mocking is bad and we shouldn’t mock?!” No.
It depends on what you are “overriding”.
- Is your business domain logic what you are mocking? Then it’s wrong.
- Is the connection to the DB what you are mocking? Then it’s right.
It depends on the context of the logic and where that logic belongs.
Is it part of your business domain logic? Then you shouldn’t mock it but instantiate it.
Is it part of any infrastructure dependency like DB connection, IO file system, Network, or any external service that has nothing to do directly with your business domain? Then mock it using abstractions/interfaces.
The interface should be the contract between your business domain logic and its external infrastructure dependencies. Imagine how easy it would be to unit test your domain logic by instantiating it and calling their methods with different arguments expecting different inputs under your entire control.
When you are writing a unit test:
- Try to instantiate your classes first.
- Avoid mocking concrete classes. I wrote an article exclusively about this exclusively: encouraging final classes and interfaces.
Mock interfaces. Instantiate concrete classes.
“Excessive use of mocks leads to legacy code.” — Philippe Boargau
How can we avoid excessive mocking?
- Favor immutable state over a mutable state.
- Make dependencies explicit.
- Program to an interface, not to an implementation.