A refactoring consists of many small, technical steps. Together, these are called the mechanics. If you follow the mechanics closely like a cookbook recipe, you should be able to learn the refactoring without much trouble.
- Identify all occurrences of code that create or obtain the collaborator.
- Apply the Extract Method refactoring to this creation code, creating the factory method (discussed on page 110 of Fowler's book; see the Resources section for more information).
- Assure that the factory method is accessible to the target object and its subclasses. (in the Java language, use the
- In your test code, create a mock object implementing the same interface as the collaborator.
- In your test code, create a specialization object that extends (specializes) the target.
- In the specialization object, override the creation method to return a mock object that accommodates your test.
- Optional: create a unit test to assure that the original target object's factory method still returns the correct, non-mock object.
Imagine you are writing the tests for a bank's Automatic Teller Machine. One of those tests might look like Listing 2:Listing 2. Initial unit test, before mock object introduction
In addition, the matching code inside the
AtmGui class might look like Listing 3:
This approach will work, but it has an unfortunate side effect: the checking account balance is lower than when the test started, making other testing more difficult. There are ways to solve that, but they all increase the complexity of the tests. Worse, this approach also requires three round trips to the system in charge of the money.
To fix this problem, the first step is to refactor
AtmGui to allow us to substitute a mock transaction for the real transaction, as shown in Listing 4 (compare the boldface source code to see what we're changing):
Back inside the test class, we define the
MockTransaction class as a member class, as shown in Listing 5:
And finally, we can rewrite our test so that the tested object uses the
MockTransaction class rather than the real one, as shown in Listing 6.
This solution yields a test that is slightly longer, but is only
concerned with the immediate behavior of the class being tested, rather
than the behavior of the entire system that lies beyond the ATM's
interface. That is, we no longer check that the final balance of the
test account is correct; we would check that function in the unit test
Transaction object, not the
Note: According to its inventors, a mock object is supposed to perform all of its own validation inside its
method. In this example, for clarity, we left some of the validation
inside the test method. As you grow more comfortable using mock
objects, you will develop a feel for how much validation responsibility
to delegate to the mock.