Getting Started with EasyMock for Unit Testing

Getting Started with EasyMock for Unit Testing

Installing EasyMock

EasyMock is a library that simplifies the creation of mock objects for specified interfaces or classes, facilitating unit testing through simulation of collaborative modules or domain objects. To use EasyMock in your project, simply add the EasyMock JAR file to your classpath.

Using EasyMock

The process of using EasyMock can be broken down into these key steps:

  • Generating mock objects with EasyMock
  • Setting expected behaviors and outputs for mock objects
  • Switching mock objects to Replay state
  • Invoking mock object methods for unit testing
  • Verifying mock object behaviors

Generating Mock Objects

EasyMock can dynamically create mock objects based on specified interfaces or classes.

Creating Mock Objects for Interfaces

Consider the ResultSet interface as an example:

public interface java.sql.ResultSet {
  public abstract java.lang.String getString(int arg0) throws java.sql.SQLException;
  public abstract double getDouble(int arg0) throws java.sql.SQLException;
  // Other methods...
}

Creating a real ResultSet object typically involves a complex process: developers often write a DBUtility class to obtain a database connection Connection, then use the Connection to create a Statement. Executing the Statement yields one or more ResultSet objects. This construction process is complex and depends on proper database operation. Issues with the database or database interaction modules can affect unit test results.

We can solve this problem by dynamically creating a mock object for the ResultSet interface using EasyMock:

import static org.easymock.EasyMock.createMock;
ResultSet mockResultSet = createMock(ResultSet.class);

Alternatively:

import org.easymock.EasyMock;
ResultSet mockResultSet = EasyMock.createMock(ResultSet.class);

Creating Mock Objects for Classes

By default, EasyMock only supports creating mock objects for interfaces. To create mock objects for concrete classes, you need to download the EasyMock Class Extension package. When mocking concrete classes, simply replace org.easymock.EasyMock with org.easymock.classextension.EasyMock.

Managing Multiple Mock Objects with IMocksControl

For more complex test cases using multiple mock objects, you can use the mechanism provided by EasyMock to create and manage mock objects.

The createControl method of the EasyMock class creates an IMocksControl object, which can create and manage multiple mock objects.

IMocksControl control = EasyMock.createControl();
java.sql.Connection mockConnection = control.createMock(Connection.class);
java.sql.Statement mockStatement = control.createMock(Statement.class);
java.sql.ResultSet mockResultSet = control.createMock(ResultSet.class);

Setting Expected Behaviors and Outputs

In a complete test, a mock object goes through two states: Record and Replay.

When a mock object is created, it is in Record state. In this state, users can set expected behaviors and outputs for the mock object. These behaviors are recorded and saved in the mock object.

The process of adding mock object behaviors consists of three steps:

  1. Make specific method calls on the mock object
  2. Use the expectLastCall static method provided by org.easymock.EasyMock to get the IExpectationSetters instance corresponding to the last method call
  3. Use the IExpectationSetters instance to set expected outputs (two types):
    • Return values
    • Throw exceptions

Setting Expected Return Values

Setting return values corresponds to the andReturn method of the IExpectationSetters interface.

IExpectationSetters<T> andReturn(T value);
Non-void Return Values

For example, using the ResultSet mock object, if we want the mockResultSet.getString(1) method to return "My return value", we would write:

mockResultSet.getString(1);
EasyMock.expectLastCall().andReturn("My return value");

Or:

EasyMock.expect(mockResultSet.getString(1)).andReturn("My return value");

If you want a method to always return the same value without setting the mock object behavior each time, you can use the default return value method — andStubReturn method.

void andStubReturn(Object value);

For example, suppose we've created mock objects for the Statement and ResultSet interfaces: mockStatement and mockResultSet. During testing, we want the executeQuery method of the mockStatement object to always return mockResultSet. We would write:

mockStatement.executeQuery("SELECT * FROM sales_order_table");
EasyMock.expectLastCall().andStubReturn(mockResultSet);

Or:

EasyMock.expect(mockStatement.executeQuery("SELECT * FROM sales_order_table")).andStubReturn(mockResultSet);
Void Return Values

For methods with a void return type, we don't need to set return values; we only need to set the number of calls (which is optional).

For example, using the close method of the ResultSet interface, if we want this method to be called 3 to 5 times during testing, we would write:

mockResultSet.close();
EasyMock.expectLastCall().times(3, 5); // Newer versions of EasyMock may omit this line

Or:

EasyMock.expect(mockResultSet.close()).times(3, 5);

Setting Expected Exceptions

Setting expected exceptions corresponds to the andThrow method of the IExpectationSetters interface.

IExpectationSetters<T> andThrow(Throwable throwable);

Similarly, setting default exceptions corresponds to the andStubThrow method of the IExpectationSetters interface.

void andStubThrow(Throwable throwable);

For example:

EasyMock.expectLastCall().andThrow(new MyException(new RuntimeException())).anyTimes();

Setting Expected Method Call Counts

  1. Setting exact call counts: Use the times method of the IExpectationSetters interface.
    IExpectationSetters<T> times(int count);
        

    For example, if we want the getString method of mockResultSet to be called 3 times during testing, with each call returning "My return value", we would write:

    mockResultSet.getString(1);
        expectLastCall().andReturn("My return value").times(3);
        

    Or:

    EasyMock.expect(mockResultSet.getString(1)).andReturn("My return value").times(3);
        
  2. Setting inexact call counts:
    • times(int minTimes, int maxTimes): The method should be called at least minTimes and at most maxTimes.
    • atLeastOnce(): The method should be called at least once.
    • anyTimes(): The method can be called any number of times.

Runtime Return Values

Sometimes, the expected return value of a method isn't fixed but depends on the input parameters. In such cases, you need to use the andAnswer method:

EasyMock.expect(mockService.execute(EasyMock.anyInt())).andAnswer(new IAnswer<Integer>() {
    public Integer answer() throws Throwable {
        Integer count = (Integer) EasyMock.getCurrentArguments()[0];
        return count * 2;
    }
});

Note: You can get the input parameters using EasyMock.getCurrentArguments()!

Switching Mock Objects to Replay State

During both the mock object generation and behavior setting phases, the mock object is in Record state, where it records user-defined expected behaviors and outputs.

Before using the mock object for actual testing, you need to switch its state to Replay. In this state, the mock object responds to specific method calls according to the defined expectations.

There are two ways to switch an object to Replay state:

  • If the mock object was generated using the createMock method:
    EasyMock.replay(mockResultSet);
        
  • If the mock object was generated using the createMock method of an IMocksControl interface object:
    control.replay();
        

    This statement switches all mock objects generated through the control's createMock method to Replay state.

Invoking Mock Object Methods for Unit Testing

This section will be covered in a JUnit + EasyMock example.

Verifying Mock Object Behaviors

After using the mock object for actual testing, you need to verify the number of method calls made to the mock object.

  • If the mock object was generated using the createMock method:
    EasyMock.verify(mockResultSet);
        
  • If the mock object was generated using the createMock method of an IMocksControl interface object:
    control.verify();
        

    Similarly, this statement verifies all mock objects generated through the control's createMock method.

Reusing Mock Objects

To avoid generating too many mock objects, EasyMock allows you to reuse existing ones.

You can use the reset method to reinitialize a mock object. After reinitialization, the mock object is set to Record state.

  • If the mock object was generated using the createMock method:
    EasyMock.reset(mockResultSet);
        
  • If the mock object was generated using the createMock method of an IMocksControl interface object:
    control.reset();
        

    Similarly, this statement reinitializes all mock objects generated through the control's createMock method.

Using Parameter Matchers in EasyMock

When using mock objects for actual testing, EasyMock matches expected method calls based on method name and parameters. By default, EasyMock uses the equals() method for parameter comparison, which can cause issues. Therefore, EasyMock provides various parameter matching approaches.

For example, with the mockStatement object created earlier:

mockStatement.executeQuery("SELECT * FROM sales_order_table");
EasyMock.expectLastCall().andStubReturn(mockResultSet);

In actual usage, you might encounter case sensitivity issues with SQL keywords (since SQL is case-insensitive), such as writing "SELECT" as "select". In this case, the default equals matcher used by EasyMock would consider the parameters not matching, and the expected method on the mock object wouldn't be called.

Predefined Parameter Matchers

  1. anyObject(): Matches any input value.
  2. aryEq(X value): Matches using Arrays.equals(), suitable for array objects.
  3. isNull(): Matches when the input value is null.
  4. notNull(): Matches when the input value is not null.
  5. same(X value): Matches when the input value is the same object as the expected value.
  6. lt(X value), leq(X value), geq(X value), gt(X value): Matches when the input value is less than, less than or equal to, greater than or equal to, or greater than the expected value, respectively. Suitable for numeric types.
  7. startsWith(String prefix), contains(String substring), endsWith(String suffix): Matches when the input value starts with, contains, or ends with the expected value, respectively. Suitable for String types.
  8. matches(String regex): Matches when the input value matches the regular expression. Suitable for String types.

For example, if you don't care about the specific SQL statement executed by mockStatement and want all input strings to match this method call:

EasyMock.expect(mockStatement.executeQuery(anyObject())).andStubReturn(mockResultSet);

Custom Parameter Matchers

When predefined parameter matchers don't meet complex requirements, you can define your own. For example, in the previous case, we might want a matcher that is case-insensitive for SQL keywords. Using anyObject isn't ideal, so we can create a custom matcher called SQLCaseInsensitiveEquals.

To create a custom parameter matcher:

  1. Implement the org.easymock.IArgumentMatcher interface:
    • The matches(Object actual) method should implement the matching logic between the input and expected values.
    • The appendTo(StringBuffer buffer) method can add information to display when matching fails.
  2. Use a static method to wrap the class implementing the interface.
package org.easymock.demo.matcher;
import static org.easymock.EasyMock.reportMatcher;
import org.easymock.IArgumentMatcher;

public class SQLCaseInsensitiveEquals implements IArgumentMatcher { 
    private String expectedSQL = null;
    
    public SQLCaseInsensitiveEquals(String expectedSQL) {
        this.expectedSQL = expectedSQL;
    }
    
    // Information to display when matching fails
    public void appendTo(StringBuffer buffer) {
        buffer.append("SQLCaseInsensitiveEquals(\"" + expectedSQL + "\")");
    }
    
    // Matching logic between input and expected values
    public boolean matches(Object actualSQL) {
        if (actualSQL == null && expectedSQL == null) return true;
        else if (actualSQL instanceof String) return expectedSQL.equalsIgnoreCase((String) actualSQL);
        else return false;
    }
    
    // Static method for the custom SQLCaseInsensitiveEquals matcher
    public static String sqlCaseInsensitiveEquals(String in) { 
        reportMatcher(new SQLCaseInsensitiveEquals(in));
        return in;
    }
}

Using the custom sqlCaseInsensitiveEquals matcher:

EasyMock.expect(mockStatement.executeQuery(sqlCaseInsensitiveEquals("SELECT * FROM sales_order_table"))).andStubReturn(mockResultSet);

Special Mock Object Types

The mock objects created so far are of the default EasyMock type, which is insensitive to the order of expected method calls and throws AssertionError for unexpected method calls. Besides this default type, EasyMock provides special mock types to support different needs.

Strict Mock Objects

Strict mock objects are sensitive to the order of method calls. They can be created as follows:

  1. Using EasyMock.createStrictMock():
    ResultSet strictMockResultSet = EasyMock.createStrictMock(ResultSet.class);
        
  2. Similarly, you can use an IMocksControl instance to create a strict mock object:
    IMocksControl control = EasyMock.createStrictControl();
        ResultSet strictMockResultSet = control.createMock(ResultSet.class);
        

Nice Mock Objects

Nice mock objects return default "invalid" values like 0, null, or false. They can be created as follows:

  1. Using EasyMock.createNiceMock():
    ResultSet niceMockResultSet = EasyMock.createNiceMock(ResultSet.class);
        
  2. Similarly, you can use an IMocksControl instance to create a nice mock object:
    IMocksControl control = EasyMock.createNiceControl();
        ResultSet niceMockResultSet = control.createMock(ResultSet.class);
        

Tags: EasyMock Unit Testing Mock Objects Java Testing Test Doubles

Posted on Fri, 08 May 2026 00:54:10 +0000 by cytech