Despite being an ardent advocate of TDD, mocking and Mockito (which is totally awesome), I’m going to argue against mocks.
It had to happen. I’ve found really clear situations where mocks have made my work harder, and it’s got to stop!
When used incorrectly Mocks do not test your code
Yep. Mocks can be the fool’s gold of TDD.
What do good tests achieve?
Back to basics then. What’s it all about?
- Really exercise the components under test
- Specify what the component is expected to do
- Speed up development and fault resolution
- Prevent waste – because developing without the right tests is probably slower and less efficient overall
Why do we mock?
- Create boundaries in tests
- Speed of execution
- Simulate the hard-to-simulate, especially those pesky boundary cases and errors
What is a mock?
For the purposes of this discussion, let’s bundle in all non-real test objects:
- Pretend test data
- Stubs – which receive calls and do nothing much
- Mocks – which can employ pre-programmed behaviours and responses to inputs, despite being just test objects
- Fakes – implementations that simulate the real thing, but are made for testing – e.g. an in-memory repository
How can mocks go wrong?
Your mock starts to work against you if:
- It makes your test implementation focused, rather than behaviour focused
- Mocking requires some obscure practices
- Mocking becomes a boilerplate ritual performed before testing
You’ll know if you’re doing this. If you do it before writing the code, it feels like trying to write the code backwards, via tests that predict the lines of code you need. If you do it after the fact, you’ll find yourself trying to contrive situations to exercise individual lines or statements, or you’ll find yourself pasting production code into your tests to make a mock behave the way you want.
Mocks have the power to bypass the real code, so we may find ourselves using the mocks to generate an alternative reality where things kind of work because the mocks happen to behave in a way which gives a sort of an answer. This seems to happen when the thing you’re mocking is quite complex.
If all tests begin with the same pasted code, then there’s something odd about your test refactoring and mocking.
So What’s The Solution?
- You ARE allowed to use real objects in tests
- Mock at heavy interface boundaries
- Refactor your code so more of your real algorithms and services can be used at test time
- You SHOULD test with real-life data
- Your fancy date algorithm may work fine with 4th July 2006, but if that’s not the sort of date your users will use, come up with more real life ones
- Make the test data describe real-world use cases so people can better understand the intent of the module
- Add mocks when you must
- Add spies to real objects to simulate hard-to-reach places as a preference to total mocking
- Consider using fakes
- Complex behaviour of simple things tests best if you can produce a fake implementation – this might even allow for changes in how the code under tests uses the services it depends on
- Test-first means that you delay thinking about the implementation
- Test behaviour, not lines of code
Test what you mean to test. Write tests first about important behaviour. Try to forget any knowledge about the implementation in the tests. Within reason, be proud to weave together a handful of collaborating objects to spin up a unit test.