Getting Started with EasyMock and JUnit

Learn to use EasyMock to create test mocks, record and replay the expectations and verify method invocations on mocked instances. We will be setting up EasyMock with JUnit 4 and JUnit 5, both.

1. EasyMock Dependencies

Include the latest version of easymock from the Maven repository into the project.

<dependency>
    <groupId>org.easymock</groupId>
    <artifactId>easymock</artifactId>
    <version>4.3</version>
    <scope>test</scope>
</dependency>

2. Test Steps with EasyMock

EasyMock framework creates the mock objects using the java.lang.reflect.Proxy object. When we create a mock object, during test execution, the proxy object takes the place of the real object. The proxy object gets its fields and methods from the interface or class we pass when creating the mock.

A typical test with EasyMock has four stages: create mock, expect, replay and verify.

  • Create Mock: Use EasyMock.mock() to create mocks of target classes whose behavior we want to delegate to the proxy objects. Generally, we mock the classes that interact with external systems or classes that should not be part of the test code.
  • Record Expectations: Use EasyMock.expect() to record the expectations from the mock objects. These expectations include simulating a method with certain arguments, the return value of the invoked method and the number of times the method should be invoked.
  • Replay: The EasyMock.replay() method makes the Mock object available. In ‘replay’ mode, when the test invokes a recorded method then the mock will return the recorded results in the previous step.
  • Verify: The EasyMock.verify() verifies that, during the test execution, all expectations were executed as recorded and that no unexpected call was performed on a mock.

We will see how to perform all these steps in section 4.

3. Setting Up EasyMock with JUnit

Before moving further, it is important to learn that we need to follow different approaches to run the tests on the basis underlying JUnit version is 4 or 5. So you can select one of the following solutions as per your project requirements.

The following solutions are used to process @Mock and @TestSubject annotations in the test class. If we are not using these annotations, then we can skip using the following solutions.

3.1. With JUnit 4

The legacy JUnit 4 uses the EasyMockRunner class to run the tests. Note that this runner only works with JUnit 4.5 or higher.

@RunWith(EasyMockRunner.class)
public class EasyMockTests {

}

In JUnit 4, we can also use the EasyMockRule instead of EasyMockRunner, with the same effect.

public class EasyMockTests {

	@Rule
	public EasyMockRule mockRule = new EasyMockRule(this);

}

3.2. With JUnit 5

In JUnit 5, Rules can’t be used anymore. The new JUnit 5 uses the EasyMockExtension class to run the tests. Since EasyMock 4.1, EasyMock ships with this JUnit 5 extension out of the box.

@ExtendWith(EasyMockExtension.class)
public class EasyMockTests {

}

4. EasyMock Demo

Let’s understand all the steps in easymock with an example. We will first a few classes and the dependencies to mock, then we will write a test for it.

4.1. System Under Test

We have a RecordService class that can be used to save Record data in a backend database. The RecordService is dependent on RecordDao to interact with database and SequenceGenerator to get the next valid sequence number used as Record id.

@Data
@NoArgsConstructor
public class Record {
  public Record(String name) {
    this.name = name;
  }

  private long id;
  private String name;
}
@Log
public class SequenceGenerator {
  private long value = 1;

  public long getNext() {
    log.info("Get Next Id in SequenceGenerator");
    return value++;
  }
}
@Log
public class RecordDao {

  public Record saveRecord(Record record) {
    log.info("Saving Record in RecordDao");
    return record;
  }
}
@Log
public class RecordService {
  private final RecordDao dao;
  private final SequenceGenerator generator;

  public RecordService(SequenceGenerator generator, RecordDao dao) {
    this.generator = generator;
    this.dao = dao;
  }

  public Record saveRecord(Record record) {
    log.info("Saving Record in RecordService");
    record.setId(generator.getNext());
    return dao.saveRecord(record);
  }
}

4.2. A Simple Test

In the given test, we are testing the RecordService.saveRecord() method. The service depends on RecordDao and SequenceGenerator. The Dao interacts with database and sequence generator also interacts with database to fetch the next record id. We need to mock both dependencies as they are out of scope for this testcase.

//Prepare mocks
RecordDao mockDao = EasyMock.mock(RecordDao.class);
SequenceGenerator mockGenerator = EasyMock.mock(SequenceGenerator.class);

The next step is to record expectations in both mocks. In the following lines, we are setting expectations of method invocations in both mocks, what value to return if method is invoked and how many times the method is expected to be invoked.

We can flexible matchers such as anyObject(), isA(), notNull() etc to write expectations that match a number of arguments. But we must return a concrete value from the result matchers such as andReturn() or andThrow() methods.

The invocation count is mentioned using once(), times(exactCount), times(min, max), atLeastOnce() and anyTimes().

Record record = new Record();
record.setName("Test Record");

expect(mockGenerator.getNext()).andReturn(100L).once();
expect(mockDao.saveRecord(EasyMock.anyObject(Record.class)))
        .andReturn(record).once()

To put the test execution in replay mode, we can use replay the mocks either one by one or combine all mocks in a single replay call.

replay(mockGenerator, mockDao);

//or

replay(mockGenerator);
replay(mockDao);

If we do not want to keep track of all mocks in the test, we can use EasyMockSupport to replay all mocks at once.

public class MockEasyTests {
	EasyMockSupport support = new EasyMockSupport();

	@Test
	public void test() {
		//...
		support.replayAll();
		//...
	}
}

In the replay mode, we perform the operation in the system under test. This shall invoke the recorded methods in expectations and return values from mock objects.

Finally, we verify the mocks that all expectations were met and no unexpected call happened on the mock objects. The syntax of verify() is similar to replay() method. Use one of the following options to trigger verification of mocks.

verify(mockGenerator, mockDao);

//or

verify(mockGenerator);
verify(mockDao);

//or

EasyMockSupport support = new EasyMockSupport();
support.verifyAll();

A complete example of the testcase, involving all the above steps, is as follows:

public class EasyMockTests {

  @Test
  public void whenSaveCorrectRecord_ItSavedSuccessfully() {
    //Prepare mocks
    RecordDao mockDao = EasyMock.mock(RecordDao.class);
    SequenceGenerator mockGenerator = EasyMock.mock(SequenceGenerator.class);

    Record record = new Record();
    record.setName("Test Record");

    //Set expectations
    //expect(mockGenerator.getNext()).andReturn(100L).once();
    mockGenerator.getNext();
    expectLastCall().andReturn((long) 100);
    expect(mockDao.saveRecord(EasyMock.anyObject(Record.class)))
        .andReturn(record).once();

    //Replay
    replay(mockGenerator, mockDao);

    //Test and assertions
    RecordService service = new RecordService(mockGenerator, mockDao);
    Record savedRecord = service.saveRecord(record);

    assertEquals("Test Record", savedRecord.getName());
    assertEquals(100L, savedRecord.getId());

    //Verify
    verify(mockGenerator, mockDao);
  }
}

4.3. A Test using Using Annotations

The previous example directly the mock() method to create mocks and then inject the mocks into the RecordService class. We can use @Mock and @TestSubject annotations to do this declaratively.

Note that all other steps i.e. recording expectations, replaying and verifying do not change. Only mocking is affected by this change.

@ExtendWith(EasyMockExtension.class)
public class EasyMockTestsWithAnnotationsJUnit5 {

	@Mock
	RecordDao mockDao;

	@Mock
	SequenceGenerator mockGenerator;

	@TestSubject
	RecordService service = new RecordService(mockGenerator, mockDao);

	@Test
	public void whenSaveCorrectRecord_ItSavedSuccessfully() {
		//test code
	}
}

4.4. A Test using Using EasyMockSupport

Apart from creating the instance of EasyMockSupport, we can extend the test class from EasyMockSupport. In this way, we can directly access the replayAll() and verifyAll() methods.

@ExtendWith(EasyMockExtension.class)
public class EasyMockTestsWithEasyMockSupport extends EasyMockSupport {

	@Test
	public void whenSaveCorrectRecord_ItSavedSuccessfully() {
		//create mock
		//record expecations

		replayAll();

		//test operation

		verifyAll();
	}
}

5. Advance Concepts

5.1. Mock vs Strict Mock vs Nice Mock

EasyMock supports three types of mock objects. Use the following methods to create mocks:

  • EasyMock.mock()
  • EasyMock.strictMock()
  • EasyMock.niceMock()

We can also use EasyMock.createMock() method to create these mocks:

//Default Mock
EasyMock.createMock(RecordDao.class);
//---or---
EasyMock.createMock(MockType.DEFAULT, RecordDao.class);

//Nice Mock
EasyMock.createMock(MockType.NICE, RecordDao.class);

//Strict Mock
EasyMock.createMock(MockType.STRICT, RecordDao.class);

The behavior of these mocks is different when verifying the recorded expectations.

  • Default Mock: A test fails if a method is called that is not expected or if a method that is expected is not called. Order of method calls does not matter.
  • Nice Mock: A test fails if a method is expected but not called. Methods that are called but are not expected are returned with a type-appropriate default value (0, null or false). Order of method calls does not matter.
  • Strict Mock: Similar to default mock except the order of method calls does matter.

Note that for mocks created by mock() and strictMock(), any unexpected method call would cause an AssertionError.

The niceMock() allows any unexpected method calls on the mock without failing the test when the method returns a type-appropriate default value.

5.2. Mocking Exceptions

In order to be able to test that a method throws the appropriate exceptions when required, a mock object must be able to throw an exception when called.

Use andThrow() method to record the expectation of an exception class.

EasyMock.expect(...)
   .andThrow(new IOException());

6. Conclusion

In this EasyMock tutorial, we learned to configure easymock with Junit and execute the tests under junit 4 and junit 5 platforms. We learned the basic concepts of testing with easymock, including test steps such as mock, expect, replay and verify.

Finally, we learned to write a complete test with an example.

Happy Learning !!

Sourcecode on Github

Comments

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.

Our Blogs

REST API Tutorial

Dark Mode

Dark Mode