Friday, February 11, 2005

Mock Objects explanation

I wrote what I think was a very good explanation of the Mock Objects approach on the junit mailing list today, so I've copied it below.

One thing I found interesting was the way I ended the message. I said, "I think Mock Objects are fun".

What a curious thing to find fun. It's true, though. Writing Mock Objects gives me a special kind of gee-whiz satisfaction that I can't exactly explain. It may have something to do with the fact that writing Mock Objects frequently involves little "tricks" like scoping changes or subclassing and overriding major functionality. In normal code, I'd consider these tricks to be an abherration and a threat to readability and maintainability. For some reason, they're acceptable to me in Mock Objects.

I wonder now if the Mock Objects I've written are horrible monstrosities full of hacks and completely unusuable to those who come after me. I hope not, and in general I don't think so. If there's a readability issue I'm worried about it's when I make a mock object than needs to implement just one method out of an interface, and I let Eclipse just code-generate the rest of the class. Sure, I move the implemented methods to the top, but it isn't exactly clear.

This seems to raise the question, though: If my very-clever Mock Objects are maintainable, then why do I hate and avoid this kind of work in my regular projects? Is it a question of scale, perhaps? I could (but don't) declare most of my Mock Object implementations as final, nothing subclasses them. Is it that there is more subclassing going on in my actual project code, and hence more need to keep things simple? I have my doubts about that as well.

Perhaps I just need to kill my darlings, but ... they're so cute.

-Kevin

The original text:

Often, many of the inputs and outputs of your system are its interaction with other classes. Those classes frequently have states or take actions that are difficult to test (sniffing packets, throwing exceptions).

In order to avoid this problem, you use a class that appears to your unit under test as though it is the tool it usually calls, but under the covers you've stripped out its functionality and replaced it with something you can test with.

Let me assume that in your example, you pass in some kind of network-access object in your constructor, which is then used by various methods. You need to test what your code does when that network object reports that it cannot contact any network at all. This is a very difficult situation to simulate with the actual code.

You create (or if you're lucky, download) a class that subclasses your network access object. That class may be called "AlwaysFailingMockNetworkAccessObject". It is bone-stupid, and is in actuality not a network access object at all. The only thing it does is fail when its access methods are called.

So in your test, you construct your class with this AlwaysFailingMockNetworkAccessObject, and then make your assertions about your class' behavior.

Another test might use a PlaybackMockNetworkAccessObject (another class I made up). This object might not contact a network at all, but accept in its constructor a set of data to return as though it came from a network. Using this, you can write a set of assertions based on a set of data.

One common first reaction to the Mock Objects approach is that "It doesn't *really* test the system" since it doesn't involve some major component. This is when Unit Testers reply, "It tests everything I'm concerned about -- I don't need to test the Network Access implementation, it isn't my problem." You'll also be doing an integration test before you release. This, however, is a unit test, and the Mock Objects approach helps you test just your unit.

No comments: