Excessive use of mocks makes tests very painful to maintain. If we stick painful mocks for too long, we’ll end up abandoning unit testing. Eventually, the system will degrade into legacy.
There are many techniques to avoid mocks. Some of the most effective involve architecture changes. Unfortunately, there are not the most straightforward to use. Re-architecting involves people and time that you may not dispose of right now. In the following posts, I’ll go over techniques that any developer can use in his day to day code to avoid mocks. These battle tested techniques that I’ve used on different projects in the past. Check the previous post if you’re interested to learn how I came to use them.
The first mock fighting small-scale technique I’ll go over is Immutable Value Objects.
What are Immutable Value Objects ?
Behind this weird name is something very simple to understand. Immutable Value Objects :
- Cannot change their state after construction
- Only depend on other Immutable Value Objects
- Don’t change the state of the whole system in any way
- Don’t do side effects, like inputs and outputs for example
Eric Evans popularized the name in the Domain-Driven Design Blue Book. Immutable Value Objects have existed for decades in functional languages though. We say these objects are immutable (they cannot change) and pure (they cannot do side effects). Here are 2 interesting properties of Value Objects :
- you can call a method any number of times with no risk of changing anything to the system
- you’ll always get the same result every time you call the same method on the same object
These by itself, can already be handy when testing.
How do they prevent mocks ?
That was a bit theoretical, so let’s see how this helps to reduce mocking.
Simpler “init path”
Let’s take it the other way round and see how side effects can lead to mocking. Every test starts with setting the state in which to run the test. Side effects make this complicated, as many objects need to collaborate to set this state up. When this becomes too painful, people start hacking around with mocks. This in turn makes the tests more fragile :
- We are not testing a “real” situation
- We need to keep this setup in line with the real code
💡 Intricate state initialization encourage people to use mocks.
Isolates parts of the system
Unfortunately, that is not all the story ! Mutable state also, tricks us into using mocks. As soon as your test deals with mutable state, there is a chance that this state is changed in the ‘real’ system. This means that some bugs might ‘escape’ unit tests and appear in end to end tests or in production. That’s where the mocks strike ! In order to detect this bug in a fast feedback loop, we’re likely to add larger scope tests and use mocks to speed them up …
💡 Mutable state and side effects make unit tests less effective.
Reduces code with side effects
But there’s another reason why Immutable Value Objects help us to avoid mocks. As we’ll try to use them more and more for the previous two reasons, we’ll need to adapt our programming style. As we’ll push more and more code in Immutable Value Objects, the ‘imperative’ part will shrink. This ‘imperative’ part is where side-effect happen. This is the part where mocking out IOs makes sense. To summarize, the more Immutable Value Objects we use, the more isolated the IOs are, and the less mocking we need.