Documentation for release 1.2_Java1.5 (August 7 2005)
© 2001-2005 OFFIS.
EasyMock is a class library that provides an easy way to use Mock Objects for given interfaces. EasyMock is available under the terms of the MIT license.
Mock Objects simulate parts of the behavior of domain code, and are able to check whether they are used as defined. Domain classes can be tested in isolation by simulating their collaborators with Mock Objects.
Writing and maintaining Mock Objects often is a tedious task that may introduce errors. EasyMock generates Mock Objects dynamically - no need to write them, and no generated code!
EasyMock by default supports the generation of Mock Objects for interfaces only. For those who would like to generate Mock Objects for classes, there is an extension available at the EasyMock home page.
easymock1.2_Java1.5.zip
). It contains a directory
easymock1.2_Java1.5
. Add the EasyMock jar file (easymock.jar
) from this directory to your
classpath.
tests.zip
to your class path and start
'java org.easymock.tests.AllTests'
.
The source code of EasyMock is stored in the zip file src.zip
.
Most parts of a software system do not work in isolation, but collaborate with other parts to get their job done. In a lot of cases, we do not care about using collaborators in unit testing, as we trust these collaborators. If we do care about it, Mock Objects help us to test the unit under test in isolation. Mock Objects replace collaborators of the unit under test.
The following examples use the interface Collaborator
:
package org.easymock.samples; public interface Collaborator { void documentAdded(String title); void documentChanged(String title); void documentRemoved(String title); byte voteForRemoval(String title); byte[] voteForRemovals(String[] title); }
Implementors of this interface are collaborators
(in this case listeners) of a class named ClassUnderTest
:
public class ClassUnderTest { // ... public void addListener(Collaborator listener) { // ... } public void addDocument(String title, byte[] document) { // ... } public boolean removeDocument(String title) { // ... } public boolean removeDocuments(String[] titles) { // ... } }
The code for both the class and the interface may be found
in the package
org.easymock.samples
in samples.zip
.
The following examples assume that you are familiar with the JUnit testing framework.
We will now build a test case and toy around with it to understand the
functionality of the EasyMock package. samples.zip
contains a modified version of this test. Our first test should check
whether the removal of a non-existing document does not lead to a notification
of the collaborator. Here is the test without the definition of the
Mock Object:
package org.easymock.samples; import junit.framework.TestCase; public class ExampleTest extends TestCase { private ClassUnderTest classUnderTest; private Collaborator mock; protected void setUp() { classUnderTest = new ClassUnderTest(); classUnderTest.addListener(mock); } public void testRemoveNonExistingDocument() { // This call should not lead to any notification // of the Mock Object: classUnderTest.removeDocument("Does not exist"); } }
For many tests using EasyMock,
we only need to import one class: org.easymock.MockControl
(in fact, EasyMock has only two non-internal classes and one
non-internal interface!).
import org.easymock.MockControl; public class ExampleTest extends TestCase { private ClassUnderTest classUnderTest; private MockControl control; private Collaborator mock; }
To get a usable Mock Object, we need to
MockControl
for the interface we would like to simulate,
MockControl
,
Sounds difficult? It isn't:
protected void setUp() { control = MockControl.createControl(Collaborator.class); // 1 mock = (Collaborator) control.getMock(); // 2 classUnderTest = new ClassUnderTest(); classUnderTest.addListener(mock); } public void testRemoveNonExistingDocument() { // 3 (we do not expect anything) control.replay(); // 4 classUnderTest.removeDocument("Does not exist"); }
After activation in step 4, mock
is a Mock Object for the Collaborator
interface that expects no calls. This means that if we change
our class under test to call
any of the interface's methods, the Mock Object will throw
an AssertionFailedError
:
junit.framework.AssertionFailedError: Unexpected method call documentRemoved("Does not exist"): documentRemoved("Does not exist"): expected: 0, actual: 1 at org.easymock.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:41) at $Proxy0.documentRemoved(Unknown Source) at org.easymock.samples.ClassUnderTest.notifyListenersDocumentRemoved (ClassUnderTest.java:78) at org.easymock.samples.ClassUnderTest.removeDocument (ClassUnderTest.java:34) at org.easymock.samples.ExampleTest.testRemoveNonExistingDocument (ExampleTest.java:27) // ...
Now we write a second test. If a document
is added on the class under test, we expect a call to mock.documentAdded()
on the Mock Object with the title of the document as argument:
public void testAddDocument() { mock.documentAdded("New Document"); // 3 control.replay(); // 4 classUnderTest.addDocument("New Document", new byte[0]); }
So in the record state (before calling replay
),
the Mock Object does not behave like a Mock Object,
but it records method calls. After calling replay
,
it behaves like a Mock Object, checking whether the expected
method calls are really done.
If classUnderTest.addDocument("New Document", new byte[0])
calls the expected method with a wrong argument, the Mock Object will complain
with an AssertionFailedError
:
junit.framework.AssertionFailedError: Unexpected method call documentAdded("Wrong title"): documentAdded("Wrong title"): expected: 0, actual: 1 documentAdded("New Document"): expected: 1, actual: 0 at org.easymock.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:41) at $Proxy0.documentAdded(Unknown Source) at org.easymock.samples.ClassUnderTest.notifyListenersDocumentAdded (ClassUnderTest.java:63) at org.easymock.samples.ClassUnderTest.addDocument(ClassUnderTest.java:29) at org.easymock.samples.ExampleTest.testAddDocument(ExampleTest.java:33) // ...
All missed expectations are shown, as well as all fulfilled expectations for the unexpected call (none in this case). If the method call is executed too often, the Mock Object complains, too:
junit.framework.AssertionFailedError: Unexpected method call documentAdded("New Document"): documentAdded("New Document"): expected: 1, actual: 2 at org.easymock.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:41) at $Proxy0.documentAdded(Unknown Source) at org.easymock.samples.ClassUnderTest.notifyListenersDocumentAdded (ClassUnderTest.java:64) at org.easymock.samples.ClassUnderTest.addDocument(ClassUnderTest.java:30) at org.easymock.samples.ExampleTest.testAddDocument(ExampleTest.java:33) // ...
There is one error that we have not handled so far: If we specify
behavior, we would like to verify that it is actually used. The current
test would pass if no method on the Mock Object is called. To verify that the
specified behavior has been used, we have to call
control.verify()
:
public void testAddDocument() { mock.documentAdded("New Document"); // 3 control.replay(); // 4 classUnderTest.addDocument("New Document", new byte[0]); control.verify(); }
If the method is not called on the Mock Object, we now get the following exception:
junit.framework.AssertionFailedError: Expectation failure on verify: documentAdded("New Document"): expected: 1, actual: 0 at org.easymock.AbstractBehavior.verify(AbstractBehavior.java:74) at org.easymock.PlayState.verify(PlayState.java:23) at org.easymock.MockControl.verify(MockControl.java:153) at org.easymock.samples.ExampleTest.testAddDocument(ExampleTest.java:34) // ...
The message of the exception lists all the missed expectations.
Up to now, our test has only considered a single method call. The next
test should check whether the addition of an already existing
document leads to a call to mock.documentChanged()
with the appropriate argument. To be sure, we check this three
times (hey, it is an example ;-)):
public void testAddAndChangeDocument() { mock.documentAdded("Document"); mock.documentChanged("Document"); mock.documentChanged("Document"); mock.documentChanged("Document"); control.replay(); classUnderTest.addDocument("Document", new byte[0]); classUnderTest.addDocument("Document", new byte[0]); classUnderTest.addDocument("Document", new byte[0]); classUnderTest.addDocument("Document", new byte[0]); control.verify(); }
To avoid the repetition of mock.documentChanged("Document")
,
EasyMock provides a shortcut. The method setVoidCallable(int times)
on MockControl
allows us to set the expected call count:
public void testAddAndChangeDocument() { mock.documentAdded("Document"); mock.documentChanged("Document"); control.setVoidCallable(3); control.replay(); classUnderTest.addDocument("Document", new byte[0]); classUnderTest.addDocument("Document", new byte[0]); classUnderTest.addDocument("Document", new byte[0]); classUnderTest.addDocument("Document", new byte[0]); control.verify(); }
If the method is called too often, we get an exception that tells us that the method has been called too many times. The failure occurs immediately at the first method call exceeding the limit:
junit.framework.AssertionFailedError: Expectation failure on method call documentChanged("Document"): documentChanged("Document"): expected: 3, actual: 4 at org.easymock.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:41) at $Proxy0.documentChanged(Unknown Source) at org.easymock.samples.ClassUnderTest.notifyListenersDocumentChanged (ClassUnderTest.java:71) at org.easymock.samples.ClassUnderTest.addDocument(ClassUnderTest.java:23) at org.easymock.samples.ExampleTest.testAddAndChangeDocument (ExampleTest.java:46) // ...
If there are too few calls, control.verify()
throws an AssertionFailedError
:
junit.framework.AssertionFailedError: Expectation failure on verify: documentChanged("Document"): expected: 3, actual: 2 at org.easymock.PlayState.verify(PlayState.java:24) at org.easymock.MockControl.verify(MockControl.java:153) at org.easymock.samples.ExampleTest.testAddAndChangeDocument (ExampleTest.java:45)
On a Mock Object returned by a MockControl
created with MockControl.createControl()
,
the order of method calls is not checked.
If you would like a "strict" Mock Object that checks the order of method calls,
use MockControl.createStrictControl()
to create its MockControl
.
If an unexpected method is called on a strict Mock Object,
the message of the exception will show the method calls
expected at this point followed by the first conflicting one.
verify()
shows all missing method calls.
For specifying return values, the MockControl
provides the methods setReturnValue([type] value)
and setReturnValue([type] value, int times)
.
[type]
is either Object
or a primitive type. These methods have to be called in record
state after the call to the Mock Object for which they specify
the return value.
As an example, we check the workflow for document
removal. If classUnderTest
gets a call for document
removal, it asks all collaborators for their vote for removal
with calls to byte voteForRemoval(String title)
value.
Positive return values are a vote for
removal. If the sum of all values is positive, the document is removed
and documentRemoved(String title)
is called on
all collaborators:
public void testVoteForRemoval() { mock.documentAdded("Document"); // expect document addition mock.voteForRemoval("Document"); // expect to be asked to vote ... control.setReturnValue(42); // ... and vote for it mock.documentRemoved("Document"); // expect document removal control.replay(); classUnderTest.addDocument("Document", new byte[0]); assertTrue(classUnderTest.removeDocument("Document")); control.verify(); } public void testVoteAgainstRemoval() { mock.documentAdded("Document"); // expect document addition mock.voteForRemoval("Document"); // expect to be asked to vote ... control.setReturnValue(-42); // ... and vote against it control.replay(); classUnderTest.addDocument("Document", new byte[0]); assertFalse(classUnderTest.removeDocument("Document")); control.verify(); }
For specifying exceptions (more exact: Throwables) to be thrown, the control
provides the methods setThrowable(Throwable throwable)
and
setThrowable(Throwable throwable, int times)
. These
methods have to be called in record state after the call to the
Mock Object for which they specify the Throwable
to be thrown.
Unchecked exceptions (that is, RuntimeException
, Error
and all their subclasses) can be thrown from every method. Checked exceptions can only be
thrown from the methods that do actually throw them.
The MockControl
provides different methods for specifying
expected call counts. To expect a fixed number of calls, we
already know
setReturnValue([type], int times)
setThrowable(Throwable throwable, int times)
setVoidCallable(int times)
.
To expect exactly one call, there are
setReturnValue([type])
setThrowable(Throwable throwable)
setVoidCallable()
.
The last method is implicitly assumed in record state for
calls to methods with void
return type which
are followed by another method call on the Mock Object, or by
control.replay()
.
To relax the expected call counts, there are additional methods.
The first group of them sets as expectation that a method
is called between minCount
and maxCount
times:
setReturnValue([type], int minCount, int maxCount)
setThrowable(Throwable throwable, int minCount, int maxCount)
setVoidCallable(int minCount, int maxCount)
.
The second group allows to specify an expected Range
of calls. Three Range
objects are available:
MockControl.ONE
expects exactly one call,
MockControl.ONE_OR_MORE
expects one or more calls and
MockControl.ZERO_OR_MORE
any number of calls:
setReturnValue([type], Range range)
setThrowable(Throwable throwable, Range range)
setVoidCallable(Range range)
.
It is also possible to specify a changing behavior for a method.
As an example, we define voteForRemoval("Document")
to
RuntimeException
for the next four calls,
mock.voteForRemoval("Document"); control.setReturnValue(42, 3); control.setThrowable(new RuntimeException(), 4); control.setReturnValue(-42, MockControl.ZERO_OR_MORE);
Convenience methods allow to specify an expected method call and a return value in one line of code. Instead of writing
mock.voteForRemoval("Document"); control.setReturnValue(42);
we may use
control.expectAndReturn(mock.voteForRemoval("Document"), 42);
As the setReturnValue()
methods, the convenience
methods are available for unspecified call counts, for
Range
call counts, for call counts of type
int
as well as for call counts between a minimum
and a maximum.
For the setDefaultReturnValue()
methods, there
are convenience methods named expectAndDefaultReturn
.
Convenience methods allow to specify an expected method call and a throwable to throw in one line of code. Instead of writing
mock.aMethod(12); control.setThrowable(new Error());
we may use
control.expectAndThrow(mock.aMethod(12), new Error());
However, this form does only work for methods with a return
type other than void
.
As the setThrowable()
methods, the convenience
methods are available for unspecified call counts, for
Range
call counts, for call counts of type
int
as well as for call counts between a minimum
and a maximum.
For the setDefaultThrowable()
methods, there
are convenience methods named expectAndDefaultThrow
.
To match an actual method call on the Mock Object with an
expectation, Object
arguments are by default compared with
equals()
. This may lead to problems. As an example,
we consider the following expectation:
mock.voteForRemoval(new String[] {"Document 1", "Document 2"}); control.setReturnValue(12);
If the method is called with another array with the same contents,
we get an exception, as equals()
compares object
identity for arrays:
junit.framework.AssertionFailedError: Expectation failure on method call voteForRemovals([Ljava.lang.String;@b66cc): voteForRemovals([Ljava.lang.String;@1546e25): expected: 1, actual: 0
To set another matcher, EasyMock provides the method
setMatcher(ArgumentsMatcher matcher)
on MockControl
. To redefine a matcher for
a method, we have to call setMatcher()
immediately after the first call to the method.
There are three predefined matchers in EasyMock:
MockControl.EQUALS_MATCHER
: The default matcher
that uses equals()
for object comparison, and
==
for primitive type comparison.
MockControl.ARRAY_MATCHER
: Matches array arguments
with Arrays.equals()
, behaves like
MockControl.EQUALS_MATCHER
for other arguments.
MockControl.ALWAYS_MATCHER
: Matches always.
To match our array expectation, we use
MockControl.ARRAY_MATCHER
:
mock.voteForRemovals(new String[] {"Document 1", "Document 2"}); control.setMatcher(MockControl.ARRAY_MATCHER); control.setReturnValue(42);
To redefine the default matcher that is used
for all methods on the Mock Object, we may call
setDefaultMatcher(ArgumentsMatcher matcher)
on MockControl
directly after Mock Object creation.
control.setDefaultMatcher(MockControl.ARRAY_MATCHER); mock.voteForRemovals(new String[] {"Document 1", "Document 2"}); control.setReturnValue(42);
The interface ArgumentsMatcher
has two methods:
boolean matches(Object[] expected, Object[] actual); String toString(Object[] arguments);
The first method is used to decide whether arguments match.
The second method is called to provide a string representation
of the arguments in case of an error. This allows us to provide
better error messages. Here is an example for a comparison
failure with MockControl.ARRAY_MATCHER
:
junit.framework.AssertionFailedError: Unexpected method call voteForRemovals(["Document 1", "Document 3"]): voteForRemovals(["Document 1", "Document 3"]): expected: 0, actual: 1 voteForRemovals(["Document 1", "Document 2"]): expected: 1, actual: 0
To define own arguments matchers, the abstract class
AbstractMatcher
is provided. A subclass of it
that does not redefine any method will behave like
MockControl.EQUALS_MATCHER
. The class provides
two additional methods:
protected boolean argumentMatches(Object expected, Object actual) { protected String argumentToString(Object argument) {
These methods allow the redefinition of matching and string representation of each argument. As an example, we provide an implementation of a matcher that only compares the first char for each string argument. For each string, only the first char followed by dots is displayed:
public class FirstCharMatcher extends AbstractMatcher { protected boolean argumentMatches(Object first, Object second) { if (first instanceof String) { first = first(first); } if (second instanceof String) { second = first(second); } return super.argumentMatches(first, second); } protected String argumentToString(Object argument) { if (argument instanceof String) { argument = first(argument) + "..."; } return super.argumentToString(argument); } private String first(Object string) { String first = (String) string; if (first == null || first.length() == 0) { return first; } return first.substring(0, 1); } }
Our test may use the EasyMock library in an incorrect way. For example, it may specify a non-compatible return value, or call verify() in record state. In these cases, EasyMock will complain with an appropriate exception at runtime.
Mock Objects may be reset by control.reset()
.
The default behavior for each method is to throw an AssertionFailedError
.
This can be changed by calling setDefaultReturnValue()
,
setDefaultThrowable()
or setDefaultVoidCallable()
in the record state. The following code configures the MockObject
to answer 42 to voteForRemoval("Document")
once
and -1 for subsequent calls as well as all other arguments
to voteForRemoval()
:
mock.voteForRemoval("Document"); control.setReturnValue(42); control.setDefaultReturnValue(-1);
On a Mock Object returned by a MockControl
created with MockControl.createControl()
,
the default behavior for all methods is to throw an AssertionFailedError
.
If you would like a "nice" Mock Object that by default allows all method calls and returns appropriate empty values (0, null or false),
use MockControl.createNiceControl()
to create its MockControl
.
The behavior for the three object methods equals()
,
hashCode()
and toString()
cannot be changed for Mock Objects created with EasyMock,
even if they are part of the interface for which the
Mock Object is created.
EasyMock 1.0 has been developed by Tammo Freese at OFFIS. The development of EasyMock is now hosted on SourceForge to allow other developers and companies to contribute.
Thanks to the people who gave feedback: Nascif Abousalh-Neto, Dave Astels, Francois Beausoleil, George Dinwiddie, Shane Duan, Wolfgang Frech, Steve Freeman, Oren Gross, John D. Heintz, Dale King, Brian Knorr, Dierk Koenig, Chris Kreussling, Robert Leftwich, Johannes Link, Rex Madden, David McIntosh, Karsten Menne, Stephan Mikaty, Ivan Moore, Ilja Preuss, Justin Sampson, Richard Scott, Joel Shellman, Shaun Smith, Marco Struck, Ralf Stuckert, Victor Szathmary, Henri Tremblay, Bill Uetrecht, Frank Westphal, Chad Woolley, Bernd Worsch, and numerous others.
Please check the EasyMock home page for new versions, and send bug reports and suggestions to the EasyMock Yahoo!Group. If you would like to subscribe to the EasyMock Yahoo!Group, send a message to easymock-subscribe@yahoogroups.com.
EasyMock Version 1.2_Java1.5 (August 7 2005)
Changes since 1.1:
expectAndDefaultReturn()
and expectAndDefaultThrow()
allow setting default
return values and throwables in one line of code
hashCode()
implementation is changed for
better performance
Changes since 1.0.1b:
Changes since 1.0.1:
Changes since 1.0:
expectAndReturn() and
expectAndThrow()
Changes since 0.8: