💡 Mock hell : when excessive use of test mocks makes refactoring extremely slow or difficult.
A few years ago, I managed to get a side project out of mock hell. Since then, I’ve been using what I learned to avoid mocks in all the projects I’ve worked on. This is the start of a series of posts about my mock-avoiding techniques.
Escape from Mock Hell
Between 2010 and 2014, I was working on a side project I called http://mes-courses.fr. Which actually means “my house shopping” in English. I wanted people to be able to do their house shopping in 5 minutes, by using a better UI for online groceries. I was using Ruby, and I had just read Growing Object Oriented Software Guided by Tests. I got a bit too excited with mocking, and was using it way too much.
I’d been practicing Test Driven Development for more than 5 years and I was expecting great results with a language like Ruby. After a few months though, I could feel that something was not going as well as it should. The test initialization code was getting longer and longer, as it included a lot of mock setup. This made the tests more complex and less readable. It also made them unreliable, as it was not rare for all my unit tests to pass while the system was not working. I was taking the habit of running my end to end test more and more often. I was also losing a lot of time maintaining the mock setup code in line with the real classes. Mocks also tricked me into the bad practice of keeping a 1 to 1 mapping between code and test files. That again increased my maintenance burden when moving code from one file to another.
It reached a point where I could not take it anymore. All these problems were pointing at mocks, so I tried to remove them from a test file. Here are the techniques I ended up using to remove them mocks :
- Value Objects
- Test Data Builders
- Test Matchers
- Hexagonal architecture
- In-memory fakes
- Proxy doubles)
The end result was beyond my hopes, as my problems almost magically disappeared. The code got simpler, I became a lot more confident about my unit tests, and they got easier to maintain. As an illustration, here is an excerpts from the diff of a rails controller test file which went through this mock diet.
What’s the long term risk ?
Basically, excessive mocking arms the maintainability of the tests. Here is what would have happened if I’d done nothing. Tests would have become so painful to maintain that I would have started to ignore or delete them. As coverage would decrease, more and more code would become untested. That’s exactly Michael Feathers’ definition of Legacy Code :
Legacy Code is code without tests. Michael Feathers
To summarize, excessive use of mocks leads to legacy code ! As most of us have learned the hard way, the further a system drifts into legacy, the lower the productivity.
💡 Excessive use of mocks leads to legacy code
Others already spoke about the dangers of mocks :
In this series of posts, I’ll go through the details of the different techniques I used to remove mocks. Here is my plan :
- Careless Mocking considered Harmful
- How Immutable Value Objects fight mocks
- Immutable Value Objects vs Mocks : Fizz Buzz
- How to use Test Data Builders to avoid mocks and keep your tests clear
- One last small scale technique to avoid mocks : Test Matchers
- Large scale techniques to avoid mocks
- Mocking in special contexts like legacy and dynamically or statically typed languages