5 May 2012

Misused Mock Objects

During the past year at work, I had trouble with mock objects several times. Often tests would break because the mocks were hard to program or to change. In one case, we missed a critical bug because our mock differed significantly from the real implementation.
Since I didn't have any experience with mocking, I didn't know how to improve any tests towards a best practice solution. I found a lot of code smells, but only for a few of them, did I know how to actually make it better. At one point I thought that mocks were more trouble than they're worth.
Fortunately, I also had some good experience with mocks during this year, which led me to the conclusion that in the bad cases, mocks were just badly used. Thinking about it now, some of the tests that I had to deal with seemed to be written under the motto: “Mock everything but the class you want to test.” Thinking about it, a much better motto would be: “Test every component in an environment that is as production-like as possible and mock only if there is a good reason to do so.” In fact, Wikipedia says the same and even lists valid reason for mocking. The valid reasons I personally experienced are the following: speed and simulating rare behavior. There are many instances where we mocked for speed: using an in-memory database instead of a remote one or stubbing calls to remote services. The rare behavior that we simulated was when testing handling of network errors. Instead of simulating real network errors (plugin out cables or intercepting packet traffic??) we just stubbed the interface that does the networking and let it throw a SocketException. That's a perfectly valid reason to use a mock!

While reading the Wikipedia article I noticed the words “indirect input” and “indirect output” of objects under test. This made me think that some uses of mocks might just be needed because of a bad software design. Before taking out the mocking framework, shouldn't we check if the code can be written with direct input and output? For small functionality, input is just method parameters and output is the method result. For bigger functionality, input is given via setters or constructor parameters and output again is the result of the method under test or getter methods called later. For even bigger functionality, some of the input (or context) will be supplied in the form of other objects that first have to be constructed. When there's lots of those objects, tests will obviously not just span the object under test, but also the classes of those other objects. Maybe you think that's too big for a unit test, but I think it's just fine for a big unit test. If those other classes have complex behaviors they will have their own unit tests that will have ran green every time before the bigger test runs. So we don't risk to test too much at once, since the lower layer of the application is already tested.

0 comments:

Post a Comment