The danger of (over)mocking

I’ve yet to see a mocking framework I actually want to use. Maybe I’ve just not seen enough use cases for one yet, since right now the number of times I would have found one useful is exactly one. Maybe I’m just not a mockist.

The fundamental problem, for me, of how I’ve seen mocking frameworks used in the wild is that it commits one of the gravest sins of software development: it increases coupling. Our tests shouldn’t care how a function/class/method/etc does its job; it need only care that it does in a verifiable manner. After all, one of the reasons to have tests in the first place is to be able to confidently refactor, and we can’t do that if the tests break even though the code’s behaviour hasn’t changed. Let me give an example: last year I saw someone write a test like this (in Python for clarity, the original was in C++):

class Class(object):
    def inc(self): pass
    def dec(self): pass
    def mul(self, x): pass
    def stuff(self, x):

def test_stuff():
    obj = Class()
    mock = MagicMock() = obj.dec = obj.mul = mock
    assert mock.mock_calls() == [,, call.mul(7), call.dec()]

The methods don’t do anything to keep things simple; in real life they were complicated functions. I consider the test above and anything like it to be a complete and utter waste of time and space. This isn’t just an example of increased coupling; it’s literally rewriting the production code in the test. What information hiding? What interface? I really consider asserting that certain calls were made to be an anti-pattern.

So what’s a developer to do? In the case above (and assuming inc, dec and mul do what we expect to self.value), I’d write this intead:

def test_stuff():
    obj = Class(3)
    assert obj.value == 3
    assert obj.value == 19

I’m well aware that in real life things are rarely this clean-cut and legacy codebases sometimes have no way of testing for what it does, but that doesn’t excuse introducing coupling into the codebase. It’s hard to present a realistic example in a blog post, but I’ve written many a test for tangled legacy networking code without resorting to the type of testing in the first example. It’s not easy, but it’s definitely doable.

I’m not even interested in mocking code at all except if it’s slow or isn’t absolutely deterministic. The usual culprits are doing anything with the file system, networking or talking to databases. And that’s for unit testing only. So Class.stuff calls some complicated function; that’s its business and I as a unit tester have no right to go poking in its internals. All I care about is the public API and that the behaviour is right. The question should always be: what’s the contract? If you’re asking questions about how... you’re doing it wrong. A good tester is like a mobster’s wife: you don’t want to know.

Now back to that one example of when I wanted to use a mock: In DConf 2013 there was a mocking presentation that expected the logger to be called with an error message if a method was called a certain way. That appealed to me because months before I’d fixed a bug that was “no error message logged when …”. I fixed it but without an accompanying test, which left a bad taste in my mouth. I just didn’t know how to write the test I needed. A mocking framework would have let me do it easily.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: