Endo-Testing: Unit Testing With Mock Objects
Endo-Testing: Unit Testing With Mock Objects
This paper was presented at the conference, “eXtreme Programming and Flexible Processes in
Software Engineering - XP2000”. © 2000 Tim Mackinnon, Steve Freeman, Philip Craig. To
be published in XP eXamined by Addison-Wesley.
Abstract
Unit testing is a fundamental practice in Extreme Programming, but most non-trivial code is
difficult to test in isolation. It is hard to avoid writing test suites that are complex, incomplete,
and difficult to maintain and interpret. Using Mock Objects for unit testing improves both
domain code and test suites. They allow unit tests to be written for everything, simplify test
structure, and avoid polluting domain code with testing infrastructure.
Keywords: Extreme Programming, Unit Testing, Mock Objects, Stubs
1 Introduction
“Once,” said the Mock Turtle at last, with a deep sigh, “I was a real Turtle.”
(Alice In Wonderland, Lewis Carroll)
Unit testing is a fundamental practice in Extreme Programming [Beck 1999], but most non-
trivial code is difficult to test in isolation. You need to make sure that you test one feature at a
time, and you want to be notified as soon as any problem occurs. Normal unit testing is hard
because you are trying to test the code from outside.
We propose a technique called Mock Objects in which we replace domain code with dummy
implementations that emulate real code. These Mock Objects are passed to the target domain
code which they test from inside, hence the term Endo-Testing. This practice is similar to
writing code stubs with two interesting differences: we test at a finer level of granularity than
is usual, and we use our tests and stubs to drive the development of our production code.
Our experience is that developing unit tests with Mock Objects leads to stronger tests and to
better structure of both domain and test code. Unit tests written with Mock Objects have a
regular format that gives the development team a common vocabulary. We believe that code
should be written to make it easy to test, and have found that Mock Objects is a good
technique to achieve this. We have also found that refactoring Mock Objects drives down the
cost of writing stub code.
In this paper, we first describe how Mock Objects are used for unit testing. Then we describe
the benefits and costs of Mock Objects when writing unit tests and code. Finally we describe a
brief pattern for using Mock Objects.
domain code causes side effects. Worse, the domain code might not even expose the features
to allow you to set the state necessary for a test.
For example, the authors have written tools to extend the development environment in IBM’s
VisualAge for Java, one of which generates template classes. This tool should not write a new
template class if one already exists in the environment. A naïve unit test for this requirement
would create a known class, attempt to generate a template class with the same name, and
then check that the known class has not changed. In VisualAge this raises incidental issues
such as whether the known class has been set up properly, ensuring that the user has the right
permissions, and cleaning up after the test should it fail – none of which are relevant to the
test.
We can avoid these problems by providing our own implementation that simulates those parts
of VisualAge that we need to run our test. We refer to these implementations as Mock
Objects. Our Mock Objects can be initialised with state relevant to the test and can validate
the inputs they have received from our unit test. In the example below, JUnitCreatorModel is
an object that generates test classes in the VisualAge workspace, and myMockPackage and
myMockWorkspace are Mock Object implementations of interfaces provided with VisualAge:
public void testCreationWithExistingClass() {
myMockPackage.addContainedType(
new MockType(EXISTING_CLASS_NAME));
myMockWorkspace.addPackage(mockPackage);
JUnitCreatorModel creatorModel =
new JunitCreatorModel(myMockWorkspace, PACKAGE_NAME);
try {
creatorModel.createTestCase(EXISTING_CLASS_NAME);
fail("Should generate an exception for existing type");
} catch (ClassExistsException ex) {
assertEquals(EXISTING_CLASS_NAME, ex.getClassName());
}
myMockWorkspace.verify();
}
There are two important points to note here. First, this test does not test VisualAge, it only
tests one piece of code that we have written or, with test-driven programming, are about to
write. The full behaviour is exercised during functional testing. Second, we are not trying to
rewrite VisualAge, only to reproduce those responses that we need for a particular test. Most
of the methods of a mock implementation do nothing or just store values in local collections.
For example, in the class MockPackage we have the method:
public void addContainedType(Type type) {
myContainedTypes.add(type);
}
2
Endo-Testing: Unit Testing with Mock Objects
3
Endo-Testing: Unit Testing with Mock Objects
myApplication.connectTo(myMockServer);
try {
myApplication.doSomething();
fail("Application server should have failed");
} catch (ServerFailedException e) {
assert(true);
}
myMockServer.verify();
}
With this approach, the mock server runs locally and fails in a controlled manner. The test has
no dependencies on components outside the development system and is insulated from other
possible real world failures. This style of test is repeated for other types of failure, and the
entire test suite documents the possible server failures that our client code can handle.
In the case of an expensive widget, we define similar unit tests. We can configure the mock
widget with the desired state and check that it has been used correctly. For example, a unit
test that checks that the widget is polled exactly once when a registration key is sent would be:
public void testPollCount() {
myMockWidget.setResponseCode(DEVICE_READY);
myMockWidget.setExpectedPollCount(1);
myApplication.sendRegistrationKey(myMockWidget);
myMockWidget.verify();
}
The mock widget lets us run tests on development machines with no actual widget installed.
We can also instrument the mock widget to verify that it was called correctly, which might not
even be possible with the real widget.
4.2 Better tests
4.2.1 Failures fail fast
Domain objects often fail some time after an error occurs, which is one reason that debugging
can be so difficult. With tests that query the state of a domain object, all the assertions are
made together after the domain code has executed. This makes it difficult to isolate the exact
point at which a failure occurred. One of the authors experienced such problems during the
development of a financial pricing library. The unit tests compared sets of results after each
calculation had finished. Each failure required considerable tracing to isolate its cause, and it
was difficult to test for intermediate values without breaking encapsulation.
On the other hand, a mock implementation can test assertions each time it interacts with
domain code and so is more likely to fail at the right time and generate a useful message. This
makes it easy to trace the specific cause of the failure, especially as the failure message can
also describe the difference between the expected and actual values.
For example, in the widget code above, the mock widget knows that it should only be polled
once and can fail as soon as a second poll occurs:
class MockWidget implements Widget {
...
public ResponseCode getDeviceStatus() {
myPollCount++;
if (myPollCount > myExpectedPollCount) {
fail("Polled too many times", myExpectedPollCount,
myPollCount);
4
Endo-Testing: Unit Testing with Mock Objects
}
return myResponseCode;
}
}
to:
5
Endo-Testing: Unit Testing with Mock Objects
As this code becomes more complex however, it becomes difficult to test cleanly because the
generic println method used in printDetails loses information about our understanding of the
domain. Instead, we can write a handler object to reify this dialogue between a stream and a
Person:
public void handleDetails(PersonHandler handler) {
handler.name(myName);
handler.age(myAge);
handler.telephone(myTelephone);
}
This separates the input and output aspects of rendering a Person on a stream. We can test
both that we have the inputs that we expect, and that a given set of values is rendered
correctly. The unit test for the handler inputs would then be:
void testPersonHandling() {
myMockHandler.setExpectedName(NAME);
myMockHandler.setExpectedAge(AGE);
myMockHandler.setExpectedTelephone(TELEPHONE);
myPerson.handleDetails(myMockHandler);
myMockHandler.verify();
}
followed by a separate unit test to check that the domain code for PersonHandler outputs
itself correctly:
void testPersonHandler() {
myMockPrintWriter.setExpectedOutputPattern(
".*" + NAME + ".*" + AGE + ".*" + TELEPHONE + ".*");
myHandler.name(NAME);
myHandler.age(AGE);
myHandler.telephone(TELEPHONE);
myHandler.writeTo(myMockPrintWriter);
myMockPrintWriter.verify();
}
These three effects mean that code developed with Mock Objects tends to conform to the Law
of Demeter [Lieberherr 1989], as an emergent property. The unit tests push us towards
writing domain code that refers only to local objects and parameters, without an explicit policy
to do so.
6
Endo-Testing: Unit Testing with Mock Objects
When the tests all run, we can extract the following interface:
public interface PersonHandler {
void name(String name);
void age(int age);
void telephone(String telephone);
void writeTo(PrintWriter writer);
}
We would then return to the Person class and adjust any method signatures to use the new
interface:
public void handleDetails(PersonHandler handler) { ... }
This approach ensures that the interface will be the minimum that the domain code needs,
following the Extreme Programming principle of not adding features beyond our current
understanding.
7
Endo-Testing: Unit Testing with Mock Objects
the domain code to work. This process can be costly and sometimes must be weighed against
the benefit of having the unit tests. However, when only a small part of a library needs to be
stubbed out, Mock Objects is a useful technique for doing so.
One important point that we have learned from trying to retrofit Mock Objects is that, in
statically typed languages, libraries must define their APIs in terms of interfaces rather than
classes so that clients of the library can use such techniques. We were able to extend
VisualAge because the tool API was written in terms of interfaces, whereas the Vector class in
version 1 of Java had many final methods but no interface, making it impossible to substitute.
With this style, the test makes clear what the domain code is expecting from its environment,
in effect documenting its preconditions, postconditions, and intended use. All these aspects are
defined in executable test code, next to the domain code to which they refer. We sometimes
find that arguing over which objects to verify gives us better insight into a test and, hence, the
domain. In our experience, this style makes it easy for new readers to understand the unit tests
as it reduces the amount of context they have to remember. We have also found that it is
useful for demonstrating to new programmers how to write effective unit tests.
We use this pattern so often that we have refactored common assertions into a set of
Expectation classes [Mackinnon 2000], which makes it quick to write many types of Mock
Object. Currently we have refactored this code into the classes, ExpectationCounter,
ExpectationList and ExpectationSet. For example, the ExpectationList class has the following
interface:
public class ExpectationList extends MockObject {
public ExpectationList(String failureMessage);
public void addExpectedItem(Object expectedItem);
public void addActualItem(Object actualItem);
public void verify() throws AssertionFailedException;
}
where the verify method asserts that matching actual and expected items were inserted in the
same order during the test, and where they don't it prints out an error message that indicates
where the differences occur. A Mock Object that cares about sequence would either extend or
delegate to an ExpectationList.
7 Conclusions
We have found that Mock Objects is an invaluable technique for developing unit tests. It
encourages better-structured tests and reduces the cost of writing stub code, with a common
format for unit tests that is easy to learn and understand. It also simplifies debugging by
providing tests that detect the exact point of failure at the time a problem occurs. Sometimes,
using Mock Objects is the only way to unit test domain code that depends on state that is
8
Endo-Testing: Unit Testing with Mock Objects
difficult or impossible to reproduce. Even more importantly, testing with Mock Objects
improves domain code by preserving encapsulation, reducing global dependencies, and
clarifying the interactions between classes. We have been pleased to notice that colleagues
who have also adopted this approach have observed the same qualities in their tests and
domain code.
8 References
[Beck 1999] Kent Beck. Extreme programming explained: embrace change. Reading Mass.:
Addison-Wesley, 1999.
[Binder 1999] Robert V. Binder. Testing object-oriented systems: models, patterns, and tools.
Reading Mass.: Addison-Wesley, 1999.
[C2] Various Authors, SingletonsAreEvil [WWW] available from:
http://c2.com/cgi/wiki?SingletonsAreEvil (Accessed: April 7, 1999)
[Gamma 1994] Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Design
patterns: elements of reusable object-oriented software. Reading, Mass.: Addison-Wesley,
1994.
[Lieberherr 1989] Karl J. Lieberherr and Ian M. Holland. Assuring good style for object-
oriented programs. IEEE Software 6(5):38-49, September 1989.
[Mackinnon 2000] Tim Mackinnon, JunitCreator [WWW] available from:
http://www.xpdeveloper.com/cgi-bin/wiki.cgi?JUnitCreator (Accessed: February 17, 2000)
[Stroustroup 1992] Bjarne Stroustroup. The design and evolution of C++. Reading Mass.:
Addison-Wesley, 1992.
9 Acknowledgements
We would like to thank the reviewers and the following colleagues for their contributions to
this paper: Tom Ayerst, Oliver Bye, Matthew Cooke, Sven Howarth, Tung Mac, Peter Marks,
Ivan Moore, John Nolan, Paul Simmons, J. D. Weatherspoon.
10 Notes
We will be posting example code at http://www.xpdeveloper.com.
11 Trademarks
VisualAge is a trademark of IBM