Five unit testing tips #4: Don't mock your way into accidental complexity
I’ve all but stopped using mock objects in my tests. The reason is that mocking have had a detrimental effect on the design of my systems. I’ve often ended up having the mocks trick me into adding a needless layer of indirection that does nothing except delegate to the next layer, just to satisfy the mocks.
For a while, I was wondering whether I was the only one with this problem, but then I saw this tutorial on JBehave, which so perfectly illustrates the problem I saw myself doing. Now, I don’t mean to pick on JBehave, or the author of this tutorial. Furthermore, this is not a particularly extreme example of the problem. Rather, the point of this posting is to show that even expert developers run into this trap.
Here is the test from the JBehave tutorial:
@Test
public void shouldProvideNotesToBePlayedInOrder() {
TabParser parser = mock(TabParser.class);
String asciiTab = "e|--------A3-B6-";
List notes = asList(A(3), B(6));
stub(parser.parse(asciiTab)).toReturn(notes);
Tab tab = new Tab(asciiTab, parser);
List actualNotes = tab.notesToBePlayed();
ensureThat(notes, equalTo(actualNotes));
}
In other words, the tab’s notes to be played should be equal to the parsed notes.
Here’s the implementation:
public class Tab {
private final List notes;
public Tab(String asciiTab,
TabParser parser) {
notes = parser.parse(asciiTab);
}
public List notesToBePlayed() {
return notes;
}
}
Whoops! We just introduced a layer in the design that does nothing. As a matter of fact, removing the Tab class from the whole example leaves us with a design that’s easier to understand.
Mocks can be great when you really, really need them. And I’m extremely impressed by the power of mockito. But used inappropriately, mocking can trick you into creating accidental complexity and extra layers of code that does nothing.
Comments:
[Bjørn Nordlund] - Feb 24, 2009
I totally agree with you about misuse of mocks and you are correct that the Tab class does not do anything.
But I’m not sure I understand, I think the example Tab class is just an illustration of something that uses the TabParser. Of cource the example could be better if the Tab class actually did something. What if the example had a class GuitarPlayer instad of Tab that actually produced music from notes using a TabParser?
[Jon] - Mar 5, 2009
This isn’t perhaps the best example for your case (I’m assuming here that your source is http://www.ryangreenhall.com/articles/bdd-by-ex…). It is not the mocks that cause the Tab class to be created but rather the customer test itself. The first reference to the Tab class is created while specifying what “Given tab ” means. Both TabBehaviour and the Tab class itself should give the clue that, given the current requirements, a tab is really just a List with no additional behaviour. However no refactoring was done in the tutorial (whether because of out of scope, oversight or author error i won’t speculate).
I personally don’t have a problem with mocks but it highly depends on the testing strategy being used. I think the problem is less about “mocks” and more about not hearing what the code is saying. I don’t think using whatever is currently fad-tool-of-the-month is going to change that problem for better or for worse. I do know that when i’m trying to build things in isolation it’s much clearer to use mocks like mockito than it is to roll my own.
Patrick Kua - Feb 24, 2009
I agree with you that a lot of people tend to overuse mocks. In fact, I blogged about this late last year (http://www.thekua.com/atwork/2008/10/the-world-…). I still think there is value in mocking, it just takes a while to understand where to use them appropriately. Unfortunately I find a lot of people don’t reach this level, listening to their tests about what it’s telling them about their design.
[Me] - Feb 24, 2009
I can’t find the source of this example so I don’t understand the reason behind the Tab class. Maybe you could explain how this class came about.
Pramatr - Feb 25, 2009
Completely agree with the overuse, I spent a couple of hours yesterday just trying to understand what was going on in one unit test. This was all due to the nature of the unit test using mocks. They are great for “some” things but use responsibly.