Mock Objects
Interpreters, Iteration 13
The CITkit library has changed based on the work you're doing in this iteration.
- Get the latest version of the JAR file from SourceForge.
- Change the return type of
createOperatorMap()
to useOperatorETIR.IOperator
instead ofOperatorETIR.Operator
. (That is, add anI
!)
The acceptance tests in CIAT make sure that everything works
together, including the map of operator algorithms from
createOperatorMap()
with a
HobbesInterpreter
.
In the last iteration, you removed any responsibility for
filling the map of operator algorithms from
HobbesInterpreter
. And yet, your unit tests are still
testing for this in
shouldInterpretOperatorExpression()
(or whatever you
called it) in HobbesInterpreterTest
.
Keep in mind that the tests for the operator algorithms are
testing the actual computations. So the only
responsibility that HobbesInterpreterTest
really has
is to make sure that the map of operator algorithms is working the
way it should. The unit tests should make sure that just the
object under test is working, not other
objects.
You don't have to use the map created by
HobbesInterpreterFactory.createOperatorMap()
. That's
the purpose of passing the map into the constructor of
HobbesInterpreter
!
The question is: what do you put into this map? If you put in any existing operator algorithms, you're back where you started from: testing both the interpretation of an operator expression and the computation itself. What you need is a fake operator-algorithm which is easy to control from just a test.
Let's at least commit to your map in the test:
- Create a new instance variable
myOperatorMap
inHobbesInterpreterTest
of typeMap<Operator, OperatorAlgorithm>
. - Initialize the instance variable in the
setUp()
method to be a new hash map. - Use this instance variable instead of the call to
createOperatorMap()
.
Red bar.
You should get a null pointer exception.
Interpreting Integers and Strings
The tests that make assertions about interpreting integers and
strings do not need anything in the operator-algorithm
map. So there's no need to put anything into the map in
setUp()
method or into the test methods for integers
and strings.
It's only shouldInterpretOperatorExpression()
that
should do anything to the map.
It's for this very reason that you're storing the map in an instance variable: so that you can control it carefully in your tests.
Putting an Operator Algorithm into the Map
You will need to add this code to
shouldInterpretOperatorExpression()
:
myOperatorMap.put(key, value);
But what to use for key
and value
?
I've already told you not to use a real Operator
and OperatorAlgorithm
. Truth be told, it's not
terrible to use real objects, but what real objects would you use?
Maybe Operator.ADD
and an
AdditionAlgorithm
? To make sure that the lookup and
application of the operator algorithm are working, you'd probably
have to make at least three assertions. Any fewer than three, a
clever (but evil) implementer would hard code an interpretation
based on your test data.
That's not bad, but it won't generalize well. (For example, so far you've had )
You could fake your own IOperator
and
OperatorAlgorithm
: create your own subclasses of these
interfaces, and use those in the test. But then you have to worry
about making those fakes work properly.
What if someone else could create those fakes for you? What if they could be created on the fly? This is one the really cool things you can do in a language (like Java) that supports reflection: create new code on the fly. It's even better when there are existing library to create the code for you!

You're going to use mock objects. The idea here
is that you want objects that kind of behave like
IOperator
s and OperatorAlgorithm
s, and
you'd like to be able to control them yourself. Java has several
mock-object libraries, jMock and EasyMock probably the most common.
For various reasons, I've found EasyMock to be better.
The term "mock" here (despite my picture of Nelson to the right) should be thought of in terms of "mock turtleneck". My dictionary on my Mac gives this definition: "not real or authentic, but without the intention to deceive".
Setting Up EasyMock
You'll use a library called EasyMock.
Go to the EasyMock
website, and download the latest ZIP file for EasyMock 2.4.
Unzip the ZIP file, and copy the easymock.jar
file
into your lib
directory. In Eclipse, add this JAR to
the build path.
Using Mock Objects
A test using mock objects follows this algorithm pattern:
- Declare mock objects to be used in the test.
- Set expectations on the mock objects.
- Replay, compute, assert, and verify.
Computation and Assertion
I usually work backwards, with the computation and assertion:
assertSame("the answer", myInterpreter.interpret( new OperatorETIR(left, operator, right) ));
assertSame()
uses ==
to make sure that
the computed object is exactly the same as the expected
value. assertEquals()
uses equals(Object)
to test equality. Use assertSame()
with
mock-object tests.
The expected value is any old string. Since the operator algorithm is mocked out, you'll be able to control what its computation returns. This should be a mock object, too, but the interpreter isn't ready for that yet.
left
, right
, and operator
should all be declared as local variables, initialized to mock
objects. Usually, only the object-under-test is
not a mock object. Make an exception here because
of the visitor pattern. The OperatorETIR
is a
secondary object-under-test. As long as its subobjects are mocks,
you're doing good mock-object testing.
Delete the current assertions in
shouldInterpretOperatorExpression()
. Add the assertion
from above. Fix the compiler error for assertSame()
.
You should have three complaints about left
,
right
, and operator
.
A Mock-Object Control
You have some compiler errors now, and you also haven't finished the last step of the mock-object test pattern. These are more easily solved with a mock object control (at least with the EasyMock library).
IMocksControl control = EasyMock.createControl();
Add this code to the beginning of the test method. (Still compilation errors.)
The control will create mocks for you as well as replay and verify the mocks.
Replay and Verify
Replaying your mocks (with a mock control) looks like this:
control.replay();
verifying like this:
control.verify();
Replay the mocks before the assertion. Verify the mocks after the assertion. (Still won't compile.)
No, I haven't told you what these do. They'll make sense after you have the rest of the code in place.
Creating Mock Objects
You need to declare local variables left
,
operator
, and right
; each one needs to be
set to a new mock object. Here's what one of those looks like:
ExpressionTIR left = control.createMock(ExpressionTIR.class);
Based on the OperatorETIR
constructor,
left
needs to be of type ExpressionTIR
.
So that's how left
is declared. As for its
initialization, the mock control is asked to create a mock using
the ExpressionTIR
interface.
It is critical that ExpressionTIR
is an
interface. Due to some language issues in Java, it's very easy
creating a mock object for an interface. You could try to use
IntegerETIR
there instead, and it will compile, but
you'll get a runtime error when you run your tests.
This is more than okay because it actually moves you in the
right direction. You don't want to pick a
IntegerETIR
or StringETIR
. The left
subtree is just any old ExpressionTIR
.
Add this definition of left
after the declaration
of control
and before the
control.replay()
. Add similar definitions of
right
and operator
.
The code will compile now, but the unit tests will red bar.
This explains the I
- prefix you'll see on a lot of
the interfaces from the CITkit and other libraries.
IOperator
is the interface for Operator
.
The more interfaces you have, the more mock-object testing you can
do.
operator
's type should be IOperator
.
This is an interface for the Operator
class.
Using Mocks
The red bar is due to a NPE because when your operator is looked
up in the operator map, it finds nothing. Maps return
null
when the key can't be found.
So you need to put your mocked operator into the operator map in your test.
myOperatorMap.put(operator, algorithm);
This uses the mocked operator
you already have.
algorithm
is new.
Keep in mind that nothing must come between the replay, compute/assert, and verify steps. All the code you add has to come before the replay.
Add this code to the test method. Define algorithm
as a mock OperatorAlgorithm
. Compile, and red bar.
Although... that red bar is different! It's complaining about the assertion!
Setting an Expectation
So what's missing from the mocks?
visitOperatorETIR()
does two things: gets an operator
algorithm from the operator-algorithm map, and apply the operator
algorithm.
You've taken care of the map.
It's the operator algorithm itself that's not working. The whole point of this iteration was to make that algorithm a blank slate. It doesn't add, subtract, divide, or do any computation. You're going to mock out its behavior.
Let's expression the expectation in words:
The algorithm should compute with the left and right subtrees. This computation should return the string
"the answer"
(since that's what's already in the assertion).
This is expressed in EasyMock like so:
EasyMock.expect(algorithm.compute(left, right)).andReturn("the answer");
Compare the English description to the code.
Add this expectation just before the replay. Green bar!
The EasyMock.expect()
method allows you to say:
"This method should be called on my mock object with these
arguments." The chained andReturn()
allows you to
specify what value that invocation will return.
Replay
When a mock object is created, it's in a "recording mode". All
of the expectations you set with EasyMock.expect()
are
recorded on the mock object. After you replay the mock (with
control.replay()
), the mock moves into a "playback
mode". Only the expectations are allowed, and when the expected
methods are called, the prescribed return values are
returned.
Comment out the one expectation. Red bar. Explore the exception that's the cause of the red bar.
Uncomment the expectation. Green bar.
Comment out the control.replay()
. Red bar. Puzzle
at the exception.
Uncomment the replay. Green bar.
From the NPE that's thrown, it's not at all clear that a
control.replay()
is missing. The moral of the story is
to always check that your mock-object tests have a replay
just before the compute/assert.
Verify
The verify step is to make sure that all of the expectations were actually triggered. Let's add an expectation that your computation code isn't satisfying.
The purpose of getPunctuation()
is to get the
punctuation used for an operator, like "+"
for
Operator.ADD
.
Add another expectation that getPunctuation()
will
be called on operator
and return some
String
(your choice). Red bar. Explore the triggering
exception.
Now what would happen if you didn't verify the mocks?
Comment out the control.verify()
. Green bar!
Yikes! You are still protected in that the computation cannot do more than you expect, but it can do less.
Uncomment the verify step. Red bar. Delete the unnecessary expectation. Green bar.
Acceptance Tests
Actually:
Green bars all around.
The CIAT tests should work, and they're providing a very
important service for you. They make sure that the
operator-algorithm map from createOperatorMap()
is
being used by HobbesInterpreter
. Of course, it doesn't
test this directly, but you exercise all of the operators in those
tests, and the other way they'd work through the driver is if the
right map were getting into the right interpreter.
"But, You've Got to Be Kidding Me?"
Take a look at the test method. How many lines of code did you have before? More? Less?
Since the test was already working, the benefits of this switch aren't immediately obvious. There's more code now, and it takes a bit more to read it all.
However, you only need to make this one assertion now. There's
no way to tweak the algorithm in visitOperatorETIR()
to hard code some values and still get the test to pass. The mock
objects prevent this kind of hard coding.
It's also eased a cognitive burden of remembering what a specific operator algorithm computes.
Most importantly, it's made the test very, very general. As more operators are added, there not even a way to change this test to deal with those operators! It's not the responsibility of this test.
Also, and more importantly, the left and right subtrees are mocked. In a future iteration, you'll need to manipulate those subtrees, and as mocks they'll be much easier to manipulate. And that's where the size of this method as it stands now will win out.