I'm resuming my battle against mock objects today. It's not that I have anything against mock objects themselves: they're a really useful testing technique. It just seems they're something that is easy to use badly if you don't pay attention.
There are essentially two uses of mock objects:
- To set up a fake environment in which the object you are testing can live
- To test what changes the object being tested makes to that environment
Thus, if I'm testing my EditUserDetailsAction
I'm going to mock up enough of the model that it can retrieve users (type 1), and I'm also going to test that after the edit is complete, the correctly modified user is sent back to be persisted (type 2).
If you're programming with Mocks you should always keep in mind which of the two you are doing in any case. Confusing them leads to tests that are either brittle or worthless.
The Java mock objects framework that we use here has two sets of methods for "priming" your mock:
- the
matchAndReturn
methods tell the mock object that it may (or may not) receive any number of calls to this method with these arguments, but if it does, return this value. - the corresponding
expectAndReturn
methods tell the mock object that it will definitely receive a call to this method with these arguments precisely once. You can use theverify
method later to ensure that all expected methods have been called.
Two uses for mock objects, two families of priming methods. I doubt I need to draw this any clearer, but I will.
If you're mocking out a method that is just a 'getter' with no side-effects, use matchAndReturn
. Otherwise, you end up with annoyingly brittle tests that will fail if the code being tested, for example, asks for a value twice instead of once, or gets the same value through a different code-path1. So long as you get the right value back, your test shouldn't care how it's being retrieved, or how many times2.
If you're mocking out a method that has side-effects, then obviously the side-effect is relevant to your test - you need to ensure that the method is (or isn't) called, and that it's not called more than once (unless it's idempotent, but that's harder to cater for). Thus, for testing that your object is making a particular change to the outside world, you'll have to use expectAndReturn
, and verify your mock at the end of the test.
1 Mock object tests will inevitably be brittle in regard to changing implementations, but you can mitigate the problem by setting up a reasonably complete default mock environment for your tests.
2 Obviously, if retrieving the value is expensive, you care. But it's not the place of the unit test to care about how efficient a certain method is.