EasyMock

Copyright (c) 2001 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.

A mock control object gives a mock object for an interface. In the setup state these two objects (the control and the mock) are used to define valid method calls and return values or thrown exceptions as well as expectations how often a method is called.

After changing into the active state, the mock object behaves exactly as specified in the setup state.

Main benefits

  • No writing of an explicit mock class is needed.
  • Supports return values and exceptions.
  • Supports changing results for the same method call.
  • Supports call count checking.
  • Supports default values for method calls.

Main drawbacks

  • The possible functionality of the mocks is fixed.
  • EasyMock does only work for interfaces.
  • EasyMock does only work with Java 2, 1.3+.


Installation

EasyMock has been tested on Windows NT and on Windows 98 using Sun's Java 1.3.1 and 1.4 beta.

  1. Java (at least 1.3) must be installed, with java on the path.
  2. JUnit (at least 3.7) must be installed and in your class path.
  3. Unzip the EasyMock zip file (easymock073.zip) in an empty directory. Add the EasyMock jar file (easymock.jar) to your classpath.
For testing, add easymocktests.jar to your class path and execute 'java de.offis.easymock.tests.AllTests'.


Usage

As an example, we use the interface Repository:
public interface Repository {

    void removeText(String name);
    String getText(String name);
    boolean exists(String name); 

    void sync() throws IOException;    
}

An empty mock object

The following examples assume that you are familiar with the JUnit testing framework and with the concept of mock objects. We will now build a test case and toy around with it to understand the functionality of the EasyMock package. To use EasyMocks, we only need one class, an instance of the MockControl interface, that is returned by the factory EasyMock. As we want to write a test case, we also import junit.framework.TestCase:
import de.offis.easymock.EasyMock;
import de.offis.easymock.MockControl;
import junit.framework.TestCase;


public class EasyMockTest extends TestCase {

    public EasyMockTest(String name) {
        super(name);
    }

    protected void setUp() {
    }
  
    public void testEasyMock() {
    }
}
To get a usable mock object, we need to
  1. get a MockControl for the Interface,
  2. get the mock object from the MockControl,
  3. specify the behaviour of the mock object (setup state)
  4. activate the mock object via the control (active state).
Sounds difficult? It isn't:
    private MockControl control;
    private Repository mockRepository;

    protected void setUp() {
        control = EasyMock.controlFor(Repository.class); // 1
        mockRepository = (Repository) control.getMock(); // 2 
    }
  
    public void testEasyMock() {
        // 3
        control.activate(); // 4
        // Use the mock object!        
    }
After activating in step 4), mockRepository is a mock object for the Repository interface that implements no behaviour. This means that if we try to call any of the interface's methods the mock will throw an AssertionFailedError:
    public void testEasyMock() {
        // 3
        control.activate(); // 4
        
        mockRepository.removeText("Text"); // throws an AssertionFailedError
    }
throws
    junit.framework.AssertionFailedError: EasyMock for interface Repository:
      Unexpected method call removeText("Text")
            at de.offis.easymock.VerifyingState.invoke(VerifyingState.java:40)
            at de.offis.easymock.AbstractMockController$1.invoke(AbstractMockController.java:20)
            at $Proxy8.removeText(Unknown Source)
            at EasyMockTest.testEasyMock(EasyMockTest.java:24)

Adding Behaviour

Now we give our mock object some behaviour. We want it to respond to mockRepository.removeText("Text"). To get this, we have to the following in the setup state:
  • call mockRepository.removeText("Text")
  • specify that it is has to be called one or more times.
    public void testEasyMock() {
        mockRepository.removeText("Text"); // 3
        control.setVoidCallable(); // 3
        
        control.activate(); // 4
        
        mockRepository.removeText("Text"); // throws an AssertionFailedError
    }
Since Release 0.7, there is also a shortcut: If a void method is called in the setup state, and no behaviour is specified, setVoidCallable() is assumed automatically. So the line control.setVoidCallable(); // 3 can be removed.

In the setup state, the mock does not behave like a mock, but it is used to make the method calls for which we define the behaviour on the control! If you want to make this more explicit, you can give it an other name in this phase:

    public void testEasyMock() {
        MockControl mockControl = EasyMock.controlFor(Repository.class);
        Repository methodCallControl = (Repository) mockControl.getMock();

        methodCallControl.removeText("Text");
        mockControl.setVoidCallable();

        mockControl.activate();

        Repository mockRepository = (Repository) mockControl.getMock();        
        mockRepository.removeText("Text"); 
    }
However, we will stick to using only the mockRepository variable.

If we call mockRepository.removeText("Text") again, nothing happens. If we call mockRepository.removeText() with another parameter (for example "Text2"), we get an AssertionFailedError:

    junit.framework.AssertionFailedError: EasyMock for interface Repository:
      Unexpected method call removeText("Text2")
            at de.offis.easymock.VerifyingState.invoke(VerifyingState.java:40)
            at de.offis.easymock.AbstractMockController$1.invoke(AbstractMockController.java:20)
            at $Proxy9.removeText(Unknown Source)
            at EasyMockTest.testEasyMock(EasyMockTest.java:26)

Verifying Behaviour

There is one error case that we have not concerned so far: If we specify a behaviour, we have to verify that it is actually needed! The following code does not show an error:
    public void testEasyMock() {
        mockRepository.removeText("Text");
        control.setVoidCallable();
        
        control.activate(); // 4
        
        // no usage of specified behaviour!
    }
To verify that all the specified behaviour has been used, we have to call control.verify():
    public void testEasyMock() {
        mockRepository.removeText("Text");
        control.setVoidCallable();
        
        control.activate(); // 4
        
        // no usage of specified behaviour!
        control.verify(); // throws AssertionFailedError
    }
Then we get the following exception:
    junit.framework.AssertionFailedError:  
    Expectation failure on verify: EasyMock for interface Repository 
      method call removeText("Text"): expected at least one call
            at de.offis.easymock.AbstractMockController.verify(AbstractMockController.java:49)
            at EasyMockTest.testEasyMock(EasyMockTest.java:27)

Expecting an explicit number of calls

Up to now, our mock only checks whether the method is called at all. We can also specify an explicit call count as parameter to setVoidCallable(). For testing, we call the method too many times and see what happens.
    public void testEasyMock() {
        mockRepository.removeText("Text");
        control.setVoidCallable(3);
        
        control.activate(); // 4
        
        mockRepository.removeText("Text");
        mockRepository.removeText("Text");
        mockRepository.removeText("Text");
        mockRepository.removeText("Text"); // throws AssertionFailedError
        mockRepository.removeText("Text");
        
        control.verify(); 
    }
We get the following exception that tells us that the method has been called too many times. The failure occurs directly at the first method call exceeding the limit.
    junit.framework.AssertionFailedError: EasyMock for interface Repository:
      method call removeText("Text"): calls expected: 3, received: 4
            at de.offis.easymock.VerifyingState.invoke(VerifyingState.java:40)
            at de.offis.easymock.AbstractMockController$1.invoke(AbstractMockController.java:20)
            at $Proxy12.removeText(Unknown Source)
            at EasyMockTest.testEasyMock(EasyMockTest.java:29)
If we delete the last three mockRepository.removeText("Text")-calls, the control.verify() throws an AssertionFailedError:
junit.framework.AssertionFailedError: 
    Expectation failure on verify: EasyMock for interface Repository 
      method call removeText("Text"): calls expected: 3, received: 2
            at de.offis.easymock.AbstractMockController.verify(AbstractMockController.java:49)
            at EasyMockTest.testEasyMock(EasyMockTest.java:29)

Specifying return values

Our interface also contains the methods String getText(String name) and boolean exists(String name). For specifying return values, the control has the methods setReturnValue([type] value) and setReturnValue([type] value, int times). [type] is either Object or a primitive type.

As an example, we specify that our mock object should respond to

  • getText("Text") with "The text you wanted" an unlimited number of times
  • getText("Text2") with null exactly three times and to
  • exists("Text") with true exactly two times.
No surprises here! For to ensure that the mock really returns the right values, we add some assertions:
    public void testEasyMock() {
        mockRepository.getText("Text");
        control.setReturnValue("The text you wanted");
        mockRepository.getText("Text2");
        control.setReturnValue(null, 3);
        mockRepository.exists("Text");
        control.setReturnValue(true, 2);
        
        control.activate();
        
        assertEquals("The text you wanted", mockRepository.getText("Text"));
        assertNull(mockRepository.getText("Text2"));       
        assertTrue(mockRepository.exists("Text"));           
        
        control.verify(); 
    }
In this case, we can see that verify() collects all the verification errors:
    junit.framework.AssertionFailedError: 
    Expectation failure on verify: EasyMock for interface Repository 
      method call exists("Text"): calls expected: 2, received: 1
      method call getText("Text2"): calls expected: 3, received: 1
            at de.offis.easymock.AbstractMockController.verify(AbstractMockController.java:49)
            at EasyMockTest.testEasyMock(EasyMockTest.java:34)

Working with Exceptions

For specifying exceptions (more exact: Throwables) to be thrown, the control has the methods setThrowable(Throwable throwable) and setThrowable(Throwable throwable, int times).

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.

As an example, we specify that our mock object should respond to

  • getText("Text") with a RuntimeException exactly two times
  • void sync() with an IOException.
The only special thing here is that to specify a behaviour for a method that throws a checked exception, you have to embed the call it in a try-catch-clause.
import java.io.IOException;
// ...
    public void testEasyMock() {
        
        mockRepository.getText("Text");
        control.setThrowable(new RuntimeException(), 2);
        
        try {
            mockRepository.sync();
            control.setThrowable(new IOException());
        } catch (IOException shouldNeverHappen) {
            throw new Error("bug in EasyMock library");
        }
       
        control.activate();
        
        // use the mock ...      
    }

Changing behaviour for the same method call

It is also possible to specify a changing behaviour for a method. You want getText("Text") to
  • answer with "The text you wanted" the first two times,
  • throw a RuntimeException the next three times,
  • answer with "The text you really wanted" afterwards?
The implementation is straightforward:
    public void testEasyMock() {        
        mockRepository.getText("Text");
        control.setReturnValue("The text you wanted", 2);
        control.setThrowable(new RuntimeException(), 3);
        control.setReturnValue("The text you really wanted");
       
        control.activate();
        
        // use the mock ...      
    }

InvalidMockUsageException

Up to this point, at least one questions is open: What happens if we do something wrong? For example, specify a wrong return value? Or calling verify() in setup mode? Or specifying an Exception that cannot be thrown by the method?

The answer is: In all this cases, a InvalidMockUsageException will be thrown.

Reusing a mock object

An easy mock can be reset by control.reset().

Using default behaviour for methods

Up to now, the default behaviour for each method is to throw an AssertionFailedError. This can be changed by calling setDefaultReturnValue(), setDefaultThrowable() or setDefaultVoidCallable() in the setup state. The following code configures the mock to answer "The text you wanted" on getText("Text") and "The default text" for all other parameters to getText():
    public void testEasyMock() {        
        mockRepository.getText("Text");
        control.setReturnValue("The text you wanted");
        control.setDefaultReturnValue("The default text");
       
        control.activate();
        // use the mock ...      
    }

Nice mocks

On a mock generated by a MockControl returned by EasyMock.controlFor(), the default behaviour for each method is to throw an AssertionFailedError. If we want a "nice mock" that by default allows all method calls and returns appropriate empty values (0, null or false), we simply use EasyMock.niceControlFor().


EasyMock is developed and maintained by Tammo Freese.

Thanks to the beta testers and first users for their helpful comments, suggestions and bug reports:
Dierk Koenig, Robert Leftwich, Karsten Menne, Frank Westphal, and Bernd Worsch.

Please check the preliminary EasyMock home page for new versions.
Please send bug reports and suggestions to Tammo Freese.

EasyMock version 0.73 (2001-07-12)

Changes since 0.7:

  • Bug fixed: equals(), hashCode() and toString() now also work with Java 1.3
  • Tests moved to own package de.offis.easymock.tests
  • Many corrections in the documentation

Changes since 0.6:

  • Bug fixed: equals(), hashCode() and toString() now cannot be redefined
  • setVoidCallable() is automatically assumed if missing
  • Support of default values for methods
  • New interface MockControl for mock controls
  • Nice mocks

Changes since 0.5:

  • Bug fixed: Return value null is now possible
  • Behaviour changed: verify() in setup state throws an InvalidMockUsageException now
  • all addReturnValue(...) - methods renamed to setReturnValue
  • all (int count) parameters renamed to (int times)
  • setOpenVoidCallCount() renamed to setVoidCallable()
  • setExpectedVoidCallCount(int times) renamed to setVoidCallable(int times)
  • addThrowable(...) renamed to setThrowable
  • AssertionFailedErrors provide information