EasyMock 1.2_Java1.3 Readme

Documentation for release 1.2_Java1.3 (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 Benefits

EasyMock Drawbacks

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.

Installation

  1. Java (at least 1.3.1) is required.
  2. JUnit (at least 3.8.1) has to be in the class path.
  3. Unzip the EasyMock zip file (easymock1.2_Java1.3.zip). It contains a directory easymock1.2_Java1.3. Add the EasyMock jar file (easymock.jar) from this directory to your classpath.
To execute the EasyMock tests, add 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.

Usage

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.

The first Mock Object

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

  1. get a MockControl for the interface we would like to simulate,
  2. get the Mock Object from the MockControl,
  3. specify the behavior of the Mock Object (record state)
  4. activate the Mock Object via the control (replay state).

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)
    // ...

Adding Behavior

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)
    // ...	

Verifying Behavior

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.

Expecting an Explicit Number of Calls

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)

Strict Mocks

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.

Specifying Return Values

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();
    }

Working with Exceptions

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.

Relaxing Call Counts

The MockControl provides different methods for specifying expected call counts. To expect a fixed number of calls, we already know

To expect exactly one call, there are

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:

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:

Changing Behavior for the Same Method Call

It is also possible to specify a changing behavior for a method. As an example, we define voteForRemoval("Document") to

    mock.voteForRemoval("Document");
    control.setReturnValue(42, 3);
    control.setThrowable(new RuntimeException(), 4);
    control.setReturnValue(-42, MockControl.ZERO_OR_MORE);

Convenience Methods for Return Values

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 for Throwables

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.

Arguments Matchers

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:

  1. MockControl.EQUALS_MATCHER: The default matcher that uses equals() for object comparison, and == for primitive type comparison.
  2. MockControl.ARRAY_MATCHER: Matches array arguments with Arrays.equals(), behaves like MockControl.EQUALS_MATCHER for other arguments.
  3. 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);	

Arguments Matchers and Error Messages

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

Defining own Arguments Matchers

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);
    }
}

Incorrect EasyMock Usage

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.

Reusing a Mock Object

Mock Objects may be reset by control.reset().

Using Default Behavior for Methods

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);

Nice Mocks

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.

Object Methods

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 Development

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.3 (August 7 2005)

Changes since 1.1:

Changes since 1.0.1b:

Changes since 1.0.1:

Changes since 1.0:

Changes since 0.8: