Why Mock frameworks rock

Ayende just commented on my post about writing delegate-based test doubles. He  rightfully highlights that through the method I've described, it becomes clunky to write interaction testing. I'll quote my previous post.

To sum it up, mocks are used when you want to test how your object interacts with another object, that's interaction testing, whereas all the others are used when you want to actually test your object's functionality

Let's forget the discussion about the value of interaction testing and mocks in general for a second. If you do decide to test how your object interacts with one of its dependencies, you're writing a mock and then using a mock framework makes sense. The API used will let you test the interaction conditions much faster than writing code. For example, making sure Initialize() is called only one, you'd have to write a fake using the following.

[Test]

public void InitializeIsCalledOnce()

{

    FakeConfigurationProvider provider = new FakeConfigurationProvider();

    int callCount = 0;

    provider.Initialize = () => { callCount++; };

 

    ClassThatDoesSomething obj = new ClassThatDoesSomething(provider);

    Assert.AreEqual(1, callCount);

}

As you can see, it's entirely doable, but it's starting to get a bit clunky.

The only reason why you wouldn't want to use a mock framework to do such interaction testing is when your dependency is provided by someone that provided a test harness.

Maybe that's where one day we'll be, with developers of a component providing you with a pre-written fake that, based on configuration, will throw at you any possible error combination, and check in which order and how often you called methods for each use-case you have. We're not there yet (or at all) but providing a component and a test harness for the users of that component seems to me a more long-term solution.

There is the question of the intrinsic value of interaction-based testing. In some cases, it is a requirement, in others it's not. Let's say my component has an Initialize() method. What is its behavior when you call it twice? It could be that the object is reinitialized transparently (if it has no side efects), or it could be that the object throws when initialization has already been done. In the second case you don't need a mock. In the first one your tests will catch any introduced error for any non-trivial task (lost a transaction, file handle gone). You could check that an Insert method only calls the repository class twice. But if you test a select after an insert and you get back two objects, you have a failing test.

The interesting question is, what if you don't detect an error, and the Insert method is idempotent. I'd have a tendency to think that those conditions, where you use an object in the wrong way and obtain the right results are probably of lower priorities than other tests. I'd never suggest you shouldn't test that, I'd suggest that writing those tests have probably lower priority than tests in the rest of the system.

Finally, in my last post I highlight the fact that writing your double using delegates, you can provide a default implementation that reacts the way you want, and you can replace the call with specific code on a per-test basis. I'd argue that in those conditions Mock frameworks will have a harder time providing the same level of features. Do you really want to redefine your mock every time? Do you really just want your mock to throw or return a simple value?

The point of my previous post was to suggest that Mock frameworks are over-used and not leveraged for what they are really good at. And they really shine when the objects you depend on don't have a behaviour that lets you receive errors when the caller is not respecting the documentation: done in the right order / too often, etc.

Mocks are a great tool, but shouldn't be used all the time as a general solution to writing tests. You have to pick the right tool for the right job.

Ads

Comment