I’ve tested my code thoroughly since I coded. As I came across good testing tools and techniques I incorporated them. One tactic I was hesitant to embrace was mocking. Initially the tools were crude and I just kept writing my own fakes and stubs, I trusted hand rolled solutions more. But as I came up against frameworks employing insanely complex approaches to things (looking at you Spring), I threw in the towel and used mocks to try and isolate just the bits I was trying to test. So, yes I use mocking, but I always do it begrudgingly. I feel that if a testing scenario requires excessive mocking it indicates a design failing somewhere and refactoring is in order.
The Anti-Pattern
The mocking anti-pattern that I’ve seen repeatedly is developers employing mocking as a response to complexity they aren’t able, or don’t want, to sort out. Instead of selectively mocking some clear interaction, and insuring the mocked interactions reflect reality they simply mock a top level method and return a large heap of type unsafe stubbed data. Often you’ll see this around REST services or persistence mechanisms. Mock a single service signature and return a map of lists, of maps of lists of strings etc. What was achieved? Well, depending on the static analysis tools employed and the quality of your co-workers, you may be able to put a check in the tested box. What was actually achieved? You’ve created a facade, possibly thick enough, to avoid detecting method signature changes, probably sufficient to hide data structure changes and certainly sufficient to obscure data type and content changes. Maybe you’ve insured that future code changes will not be tested at all since your tests will go on passing forever! It may take several production bugs before it’s detected and the layers of paint might be so think and dry by then it gets put on the ever growing TODO list of tech debt.
Avoiding The Anti-Pattern
TLDR: Don’t over mock.
But Really…
Ok:
- The tough love answer is, if it’s your code stack, employ strategies like Test Driven Development, testing the pieces in manageable chunks as you go. Make sure the underpinning code is tested and don’t heap on the business logic in the last layer of code. With practice you’ll find that (a) your design will be better for it and (b) that the further out in the onion you work, the less testing you actually need.
- Don’t shoot for black box tests, think gray box at a minimum. Make sure you get in under the hood some.
- Don’t mock up elaborate results ad-hoc. Can you get the result data through some means that will change as the code/data changes? Can you independently verify that the data you are using reflects reality? Is every case of an enumeration included? Use some sort of fakers, not constants, to create leaf types so that bounds cases are found.
- Bind types and signatures to reality. Write typesafe code and employ the types and interfaces in the mocks. Refactoring should immediately impact your mocked methods and data.