Here’s an anti-pattern that I overheard at work today that illustrates some of the evils of testing the way many (most?) people typically look at it.

Me: “I’m having some trouble with this code, it’s not behaving the way I’m expecting it to… ?”
Co-worker: “I wrote unit tests for this and it passed that…”

These tests were mocked out tests of our code calling various libraries.

Essentially the code looks something like:

int function(x) {
  int y = runX(x);
  return runY(y);
}

Of course the code isn’t that simple.

The test, on the other hand:

...
// Setup mocked calls
expect(runX(23)).return(42);
expect(runY(42)).return(69);

// Test function()
assert(69 == function(23));
...

The “test” is simply a restatement of the code itself. If the library behaves in any way different from what you’re expecting, the test counts for exactly zero; the test doesn’t test anything beyond the type safety that’s already enforced by the compiler. (Ok, I’m assuming a typical typed compiled language like C, C++, C#, Java, etc.)

You get a false sense of accomplishment. Your code coverage percentage ticks up a bit. You sleep better at night.

Up until you get woken up from that nice sleep when your code breaks in production in the middle of the night.

– = –

So what should you do?

Mocking should be reserved for things outside of your control. Things like network, disk, or database access springs to mind immediately.

If I’m mocking something, my general rule is that unless it’s the off-machine access I just talked about I need to mock two levels down. If I mock at all.

The function above? What would you “test” with any mocking? All you’re testing is that the compiler can compile.

– = –

Let’s do a more complex example…

int function(x) {
  int y = runX(x);
  if (y < 42) {
    return runA(y);
  } else {
    return runB(y);
  }
}

The typical call would be to mock runX and ensure that based on its return value either A or B gets called as appropriate.

I say no.

Make a function that tests the “complex” bit — the logic of the conditional — and test that!

int function(x) {
  int y = runX(x);
  if (thereBeDragons(x)) {
    return runA(y);
  } else {
    return runB(y);
  }
}

Boolean thereBeDragons(int x) {
  return x < 42;
}

Not only does the original function read cleaner, but you can test the business logic very easily since you have a function with no dependencies other than its inputs.