Mock Object testing can cause trouble - here's why and possibly how to avoid it
I got this question on the israel agile development list:
I wonder about the usefulness and pitfalls of using mock objects, I'm currently using NMock, and I'd made a simple refactoring that resulted in a holy mess when half my tests broke. I read several article about this, and there seems to be some controversy in the issue. (The latest one is http://jeremydmiller.blogspot.com/2005/06/tdd-design-starter-kit-state-vs.html ).
Mocks are great for some things. The problem you were facing could have been because of two things:
1 – not enough code reuse in the tests
Many times, refactoring the code can lead to big refactoring in the tests (adding a parameter to a constructor for example). To overcome this there are various simple rules one needs to take care of in the tests to avoid having a big chunk of tests go boom, and be able to fix such a thing form one single spot or two: code reuse.
- always create the tested object through a factory method (only one place where the ctor is called
- when performing the same action in multiple tests – refactor to use a helper method, so the call to the object is written only in one place
- and so on…
2 – Mock objects assume inner workings of object so when the inner working of an object changes, (though the functionality may be the same), the test will fail.
As a rule – tests involving mock objects (interaction testing) assume more about the inner workings of an object (white box testing) than regular tests (state based).
If you'd like to alleviate this problem a bit, there are ways to make the test act like a state based test but still use mock objects to remove some of the dependencies. For example, use Stub objects or mock objects at a level further down the chain of interaction to receive data after it has "left" the main environment on which you test.
An example of this might be that you'd like to test an email message, where instead of replacing the mail sender object with a mock, you replace the an object further down the chain (the mail server? The TCP stack?). When stuff is taken one level further away, the odds are not matter what refactoring you do, the outcome on the receiving end, although in some form of "raw" state, should be the same, and so acts much like state based. Only this time the state that you test is located much closer that it would have been without mock objects..
You can also check out the latest presentation slides and code from agile