DEV Community

Jan Van Ryswyck
Jan Van Ryswyck

Posted on • Originally published at janvanryswyck.com on

Blaming Mocks For Bad Design

A while back I stumbled upon this talk titled Built-in Fake Objects. After I’ve watched the first 20 minutes or so, I was so worked up that I almost threw away my iPad just out of complete annoyance. I did eventually came back to watch the rest of the talk. But what bothered me so much?

Well, the general theme of this talk is that using mocking frameworks is bad. In itself, there’s really nothing extraordinary about that. There are so many talks out there that all compete to bring you this Luddite message. But what particularly bothers me in this case are the arguments made against using mocking frameworks.

This is the example used to demonstrate that mocks are the reincarnation of the devil itself.

String report(User user) {
    return String.format(
        "Balance of %s is %d USD",
        user.profile().name(),
        user.account().balance().usd()
    );
}

This is the simple implementation of a method named report. It returns a string containing some information about a specified user. The following code snippet shows the implementation of the accompanying unit test.

@Test
void printsReport() {
    Profile profile = mock(Profile.class);
    Account account = mock(Account.class);
    doReturn("Jeffrey").when(account).name();
    Balance balance = mock(Balance.class);
    doReturn(123).when(balance).usd();
    doReturn(balance).when(account).balance();
    User user = mock(User.class);
    doReturn(profile).when(user).profile();
    doReturn(account).when(user).account();
    assertThat(
        Foo.report(user),
        containsString("Balance of Jeffrey is 123 USD")
    );
}

First of all, I’m not going to disagree that the test code looks pretty bad. I just wonder how intentional the omission of blank lines actually was. But using this unit test as the poster child example of why one should never use a mocking library ever again is purposely jumping to conclusions. To be fair, I would never use mocks for this implementation either. But that doesn’t automatically mean that mocking libraries should be avoided at all cost, just because they shouldn’t be applied in a large number of cases. Its basically the same as saying that all web frameworks are evil because using them to create desktop applications causes a lot of pain. In a sense, one cannot argue with that. But I wouldn’t be so dogmatic about it either.

In my previous post, I argued that unit tests amplify the signals that are being sent by the design of the system. If this is the case, then using mocks in the test code amplify those same signals with a factor ten or even more. And instead of shooting the messenger, we should learn how to listen instead.

Again, I wouldn’t use mocks for testing the report method. I would just use plain old state verification instead, creating an instance of a User using the Test Data Builder pattern and passing this instance as an argument to the report method. No need for any more complexity as this would reduce the implementation of the unit test to just a couple of lines of code.

But this is not the path taken in this particular example. So let’s run with it to see how mocks are telling us that the design of the system is flawed. First of all, a mock is used for a Profile, and an Account and a Balance. Even the User is a mock object. Interfaces have been put in place in order to create these mock objects.

Structure of interfaces in the domain model

Notice how these interfaces only have properties. They consist only of data, not behaviour. This is alarm bell number 1. Now let’s have a closer look at the test code itself. Notice how mocks are returning instances of other mocks. This is alarm bell number 2. Then having a closer look at the implementation of the report method. Notice the chaining of the properties in order to get the necessary data. Bingo! Alarm bell number 3. We found a train wreck. A clear violation of the Law of Demeter.

Fixing this design issue would drastically improve the test code if we still wanted to hang on to the decision of using mocks. For me this drives home the point that developers tend to blame their unit tests, the use of mocks, etc. instead of blaming their own design choices.

Presenting such a dogmatic view that mocking frameworks are evil is quite harmful in itself. It doesn’t help anyone, especially those who are new to Test-Driven Development. I strongly believe that developers should educate themselves about the particular scenarios in which mocking frameworks are an excellent choice and when they are not a good option. This tension field, this choice between applying state verification or behaviour verification is also called the “Uncertainty Principle of TDD”. Personally, I tend to use test doubles when the class under test communicates with collaborators that cross a dependency inversion boundary which is clearly not the case in this particular example.

During the second part of the talk, the speaker presents his solution to the problem by using fakes instead of mocks. A fake is an object that, from an outside view, simulates the exact same functionality and behaviour as the real implementation that it replaces. Using fakes in a unit test is another anti-pattern, which is a whole new blog post in and of itself.

My conclusion it this: mocking libraries are a valuable tool when applied correctly. We should learn by making mistakes and by listening to the signals that the design of both the system as the tests themselves are sending us. For me this is the path to enlightenment, and not by throwing the baby out with the bath water.

Top comments (0)