Mocks that specify essential implementation

James Shore said something in two tweets that I’ve abbreviated as follows:

I’ve never liked mock objects. At best, they’re a necessary evil. At worst, a meaningless “test” that overspecifies the code under test.

I tweeted back that “I’m finding mocks that overspecify the code under test are a sign that I’m making code that doesn’t express intent well.”

Here’s something of what I mean by that. Suppose I want to describe, in English, what it means for a function to make an X509 certificate from some Base64-encoded text and store it in a particular file. The method is part of a Contact object. Here’s the description:

Contacts, as you know from an earlier description [test], are created with a pile of information contained in a hash. One bit of relevant information is the :organization. There are also some Base64-encoded keys. Let’s pick one at random, call it :a_key.

In order for the Contact to create and stash the certificate, it collaborates with two objects. One of them is the :certificate_maker; the other is a :folder that knows how to stash info into a named file. The :certificate_maker is hardwired into the Contact, but the :folder is passed in.

In order to stash :a_key in a :folder, the :stash_this_key method has to do two things:

  1. It has to hand the (Base64-decoded) certificate info (the key) to the :certificate_maker, which will return some sort of encoded gibberish.

  2. The :folder has to be told to stash the gibberish into one of its files. The filename is of the form organization-name.a_key.pem. (The organization name is made according to the rules I already told you for sanitizing organization names.)

Here’s the actual test. I think you can see how it parallels my description:

It does specify implementation. Most importantly, it says that there should be a completely separate CertificateMaker object. That’s a good thing. Had I not been committed to mocking, I would have been tempted to just copy-and-paste the code I was working from, which looked something like this:

Had I done so, I would have likely never bothered to figure out what sort of magic thing "openssl x509 -inform der" does. As it was, I had to know enough to make a decent class and method name, meaning I had to learn my domain.

Given that the hard part of object-oriented design is figuring out what objects should exist and how they should interact, something that forces me to think harder about interactions and responsibilities might well be a win.

That said, there’s a constant temptation to make the implementation “incidental”, to describe what does happen in this implementation rather than what ought to happen in any reasonable implementation that fits within the, uh, conceptual domain of the program. For example, Ruby already has abstractions for file systems: the Dir and File classes. If my mock tests for Contact talked about how File and Dir are called, … that would be bad. Instead, test expressiveness forced me to condense down everything Ruby lets me do into the more limited idea of a PreferredFolder that knows how to “stash” strings.

I grant you that I still feel like I’m going overboard when I start with 27 lines of sample code and later discover I’ve got a RemoteDirectory and a Directory and a Contact and a CertificateMaker — all just to snatch something off the network, convert it, and stuff it into a file. Certainly, I’m going slower than I could (in the early part of the project), but I don’t necessarily think that’s a bad thing. I trust that it’ll keep the cost of change curve flatter — though my trust in myself might be misplaced. Wouldn’t be the first time.

3 Responses to “Mocks that specify essential implementation”

  1. MarkKnell Says:

    @Jim
    > I’ve never liked mock objects. At best, they’re a necessary evil.

    Hey, what’s wrong with necessary evil? I say this as a .NET programmer.

    But seriously, your SSL example is not my ideal example of how tests inform domain knowledge. It’s on a seam between application and OS or framework-level services. I tend to regard those things as a sort of borderlands, where some of my test habits might be a little too citified for life on the frontier. For me, unit tests make the most sense when I can slide a surrogate between the SUT and its collaborators. Near the OS, I’ve run out of room.

    I tend not to test my facades. Do you? Should I? (Should that have been a tweet?)

  2. MarkKnell Says:

    Oops, forgot to say, A, everything after the wisecrack was not aimed at Jim Shore; it’s on this blog deliberately. And B, your larger points seem incontrovertible. If I can’t mock something elegantly (or, y’know, if not elegantly, then without wanting to vomit in my shirt), it’s usually time for better design. Don’t shoot the messenger.

  3. Brian Marick Says:

    Mark: What I’m doing here is sequestering the OS/framework-level code into the smallest possible volume. I’ve pushed certificate making into the a class with a single method that is the Open3.popen… code above. That code isn’t unit tested.

    So, yes: I agree with you about the borderland, but I do think the border that’s relevant to *this* program is usefully broken apart into tightly-defined ideas/services that have some possibly not-so-clean interface to the real suppliers.

Leave a Reply

You must be logged in to post a comment.