EasyMock Tutorial: Mocking with EasyMock 5 and JUnit 5

EasyMock tutorial with JUnit 5. Create mocks, record expectations, replay and verify, mock void methods, use matchers, captures and nice or strict mocks.

EasyMock test lifecycle with create mock, record expectations, replay and verify stages

EasyMock is a Java mocking library that creates mock objects for interfaces and classes, records the calls we expect on them, replays those calls during the test, and verifies afterwards that every expected call happened. We use the mocks in place of real dependencies, so a unit test checks one class without a database, a mail server or a remote API.

EasyMock fits unit tests of service classes that depend on repositories, gateways or clients, and it still runs in many older code bases that use its record and replay style. The following example mocks the three dependencies of a customer sign-up service and walks through all four steps.

SequenceGenerator generator = mock(SequenceGenerator.class);   // 1. create mocks
CustomerDao dao = mock(CustomerDao.class);
WelcomeMailer mailer = mock(WelcomeMailer.class);
expect(generator.getNext()).andReturn(100L);                    // 2. record: getNext() returns 100
expect(dao.save(anyObject(Customer.class))).andAnswer(() -> getCurrentArgument(0));
mailer.sendWelcome(anyObject(Customer.class));                  // 2. record a void call
replay(generator, dao, mailer);                                 // 3. switch to replay mode
CustomerService service = new CustomerService(generator, dao, mailer);
Customer saved = service.register(new Customer("Lokesh"));
long id = saved.getId();                                        // 100
verify(generator, dao, mailer);                                 // 4. passes, all calls happened

Notice that the void method sendWelcome() is recorded by calling it on the mock before replay(). We set up EasyMock 5.7.0 with JUnit 5 and go through annotations, matchers, captured arguments, exceptions, the three mock types and a comparison with Mockito.

1. EasyMock Dependencies

Add the easymock artifact with the test scope. It brings ByteBuddy, ASM and Objenesis, which EasyMock needs to create mocks of classes.

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

EasyMock 5 replaced cglib with ByteBuddy for class mocks, and version 5.7.0 (August 2026) adds support for JDK 26 and a mock() method without a class argument. The examples use EasyMock 5.7.0, JUnit 6.1.3 and Java 25. The JUnit Jupiter extension works the same with JUnit 5 and JUnit 6.

2. Test Steps with EasyMock

A typical test with EasyMock has four stages, namely create, expect, replay and verify. A mock starts in record state, where every call we make on it becomes an expectation instead of real behavior. After replay(), the same mock answers calls from the code under test with the recorded results.

EasyMock test lifecycle with create mock, record expectations, replay and verify stages
A mock records calls until replay(), answers them during the test, and verify() reports expected calls that never happened
  • The create step uses EasyMock.mock(Type.class) to build a mock. For an interface, EasyMock uses a java.lang.reflect.Proxy, and for a class, it generates a subclass with ByteBuddy.
  • In the expect step, expect(mock.method(args)) records a call with its arguments, its result from andReturn(), andThrow() or andAnswer(), and how often it may happen.
  • In the replay step, replay(mocks…) switches the mocks to replay mode, and the test runs the code under test.
  • In the verify step, verify(mocks…) fails when an expected call did not happen. Unexpected calls fail earlier, at the moment they happen.

The examples follow these steps in section 4, and the strictness of each step depends on the mock type, as section 6 shows.

3. Setting Up EasyMock with JUnit

Tests that create mocks with mock() need no setup at all. Only the @Mock and @TestSubject annotations need a JUnit integration that processes them.

3.1. With JUnit 5 and JUnit 6

Since EasyMock 4.1, the EasyMockExtension processes the annotations for JUnit Jupiter tests. It creates the @Mock fields and injects them into the @TestSubject field before each test.

@ExtendWith(EasyMockExtension.class)
class CustomerServiceExtensionTest {

    @Mock
    SequenceGenerator generator;

    @Test
    void mockIsCreated() {
        assertNotNull(generator);
    }
}

3.2. With JUnit 4

JUnit 4 tests use the EasyMockRunner class with @RunWith, or the EasyMockRule when the class already needs another runner. Both need JUnit 4 on the test classpath.

// does not compile: needs JUnit 4 (junit:junit) on the classpath
@RunWith(EasyMockRunner.class)
public class CustomerServiceJUnit4Test {
}

public class CustomerServiceRuleTest {
    @Rule
    public EasyMockRule mocks = new EasyMockRule(this);
}

JUnit 5 ignores runners and rules, so a project that moves to JUnit 5 replaces both with @ExtendWith(EasyMockExtension.class).

4. EasyMock Demo

Say an online shop has a sign-up form. When a customer registers, the shop takes the next id from a database sequence, saves the customer and sends a welcome email. In a unit test of the sign-up logic, we want none of these three side effects, so we mock all three dependencies.

4.1. System Under Test

The CustomerService class depends on SequenceGenerator for the next id, CustomerDao for the database and WelcomeMailer for the email. The dependencies are interfaces, which is the most common setup in service code.

class Customer {
    private long id;
    private final String name;

    Customer(String name) {
        this.name = name;
    }

    long getId() { return id; }
    void setId(long id) { this.id = id; }
    String getName() { return name; }
}
interface SequenceGenerator {
    long getNext();
}
interface CustomerDao {
    Customer save(Customer customer);
    List<Customer> search(String name, int limit);
}
interface WelcomeMailer {
    void sendWelcome(Customer customer);
}
class CustomerService {
    private SequenceGenerator generator;
    private CustomerDao dao;
    private WelcomeMailer mailer;

    CustomerService() {
    }

    CustomerService(SequenceGenerator generator, CustomerDao dao, WelcomeMailer mailer) {
        this.generator = generator;
        this.dao = dao;
        this.mailer = mailer;
    }

    Customer register(Customer customer) {
        customer.setId(generator.getNext());
        Customer saved = dao.save(customer);
        mailer.sendWelcome(saved);
        return saved;
    }
}

The fields are not final, and the class has a no-argument constructor. Both details matter for the @TestSubject injection in the annotation test.

4.2. A Simple Test

The test creates the mocks, records the expected calls, switches to replay mode, runs register() and verifies the mocks. The once() call states the default call count explicitly, and expectLastCall() refers to the void call recorded right before it.

class CustomerServiceTest {

    @Test
    void registersCustomer() {
        SequenceGenerator generator = mock(SequenceGenerator.class);
        CustomerDao dao = mock(CustomerDao.class);
        WelcomeMailer mailer = mock(WelcomeMailer.class);

        expect(generator.getNext()).andReturn(100L).once();
        expect(dao.save(anyObject(Customer.class))).andAnswer(() -> getCurrentArgument(0)).once();
        mailer.sendWelcome(anyObject(Customer.class));
        expectLastCall().once();
        replay(generator, dao, mailer);

        Customer saved = new CustomerService(generator, dao, mailer).register(new Customer("Lokesh"));

        assertEquals(100L, saved.getId());
        assertEquals("Lokesh", saved.getName());
        verify(generator, dao, mailer);
    }
}

The andAnswer() call computes the result at call time, and getCurrentArgument(0) returns the first argument of the current call. Here the mock DAO returns the same customer it gets, which is how a real repository behaves after an insert.

4.3. A Test Using Annotations

With @Mock and @TestSubject, EasyMock creates the mocks and injects them into the fields of the class under test by type. The other steps, recording, replay and verification, do not change.

@ExtendWith(EasyMockExtension.class)
class CustomerServiceAnnotationTest {

    @Mock
    SequenceGenerator generator;

    @Mock
    CustomerDao dao;

    @Mock(type = MockType.NICE)
    WelcomeMailer mailer;

    @TestSubject
    CustomerService service = new CustomerService();

    @Test
    void registersCustomer() {
        expect(generator.getNext()).andReturn(7L);
        expect(dao.save(anyObject(Customer.class))).andAnswer(() -> getCurrentArgument(0));
        replay(generator, dao, mailer);

        assertEquals(7L, service.register(new Customer("Anna")).getId());
        verify(generator, dao);
    }
}

Create the @TestSubject with a no-argument constructor and non-final fields. A field initializer such as new CustomerService(generator, dao, mailer) runs before the extension creates the mocks, so it passes null values. If the service stores them in final fields, the injection does not replace them, and the test fails with a NullPointerException.

4.4. A Test Using EasyMockSupport

Passing every mock to replay() and verify() gets tedious with four or five mocks, and a forgotten one goes unverified. A test class that extends EasyMockSupport creates mocks through the support object, which remembers them, so replayAll() and verifyAll() handle all of them.

class CustomerServiceSupportTest extends EasyMockSupport {

    @Test
    void registersCustomer() {
        SequenceGenerator generator = mock(SequenceGenerator.class);
        CustomerDao dao = mock(CustomerDao.class);
        WelcomeMailer mailer = mock(WelcomeMailer.class);

        expect(generator.getNext()).andReturn(3L);
        expect(dao.save(anyObject(Customer.class))).andAnswer(() -> getCurrentArgument(0));
        mailer.sendWelcome(anyObject(Customer.class));
        replayAll();

        new CustomerService(generator, dao, mailer).register(new Customer("Ravi"));

        verifyAll();
    }
}

Inside the subclass, mock() is the instance method of EasyMockSupport, not the static EasyMock.mock(). A test class that already extends another class can hold an EasyMockSupport field and call support.mock(), support.replayAll() and support.verifyAll() on it.

5. Recording Expectations

An expectation says which call we expect, what it returns and how often it may happen. If we give no count, EasyMock expects one call.

5.1. Return Values and Call Counts

The result methods and the count methods chain after expect(), or after expectLastCall() for void methods.

MethodMeaning
andReturn(value)Returns a fixed value
andAnswer(() -> …)Computes the value at call time, with access to the arguments
andThrow(exception)Throws the exception
andVoid()Returns normally from a void method, useful after an earlier andThrow()
once(), times(n), times(min, max)One call, n calls, or between min and max calls
atLeastOnce(), anyTimes()One or more calls, or any number including zero
SequenceGenerator generator = mock(SequenceGenerator.class);
expect(generator.getNext()).andReturn(1L).andReturn(2L).andThrow(new IllegalStateException("Sequence exhausted"));
replay(generator);
long first = generator.getNext();                  // 1
long second = generator.getNext();                 // 2
long third = generator.getNext();                  // IllegalStateException: Sequence exhausted

Chained results run in order, so the third call throws. The same chain is the way to test retry code, where the first call fails and the second succeeds.

5.2. Void Methods

A void method cannot go inside expect(), because expect() needs a value. We call the void method on the mock in record state, and after that expectLastCall() returns the options for that call.

WelcomeMailer mailer = mock(WelcomeMailer.class);
mailer.sendWelcome(anyObject(Customer.class));
expectLastCall().andThrow(new IllegalStateException("SMTP server down"));
replay(mailer);
CustomerDao dao = mock(CustomerDao.class);
SequenceGenerator generator = mock(SequenceGenerator.class);
expect(generator.getNext()).andReturn(1L);
expect(dao.save(anyObject(Customer.class))).andAnswer(() -> getCurrentArgument(0));
replay(dao, generator);
Customer failed = new CustomerService(generator, dao, mailer).register(new Customer("Mia"));   // IllegalStateException: SMTP server down

The test shows that register() passes the mail error on to the caller. In a real shop, we might decide to save the customer anyway and retry the email later, and the same mock setup checks that behavior.

5.3. Argument Matchers

By default, EasyMock compares arguments with equals(). Matchers such as anyObject(), isA(), anyInt(), eq(), startsWith() and notNull() match a range of values instead. When one argument uses a matcher, all arguments of that call must use matchers.

CustomerDao dao = mock(CustomerDao.class);
expect(dao.search(startsWith("Lok"), anyInt())).andReturn(List.of(new Customer("Lokesh")));
replay(dao);
int found = dao.search("Lokesh", 10).size();       // 1
CustomerDao other = mock(CustomerDao.class);
IExpectationSetters<List<Customer>> mixed = expect(other.search("Lok", anyInt()));   // IllegalStateException: 2 matchers expected, 1 recorded.

The second recording fails, because “Lok” is a plain value and anyInt() is a matcher. Wrap the plain value in eq(“Lok”) to fix it.

5.4. Capturing Arguments

Sometimes the test needs to look at the object the code passed to a mock, such as the customer that reached the DAO. A Capture stores the argument of the call, and we read it after the test runs.

CustomerDao dao = mock(CustomerDao.class);
Capture<Customer> captured = newCapture();
expect(dao.save(capture(captured))).andAnswer(() -> getCurrentArgument(0));
replay(dao);
Customer saved = dao.save(new Customer("Lokesh"));
String name = captured.getValue().getName();       // "Lokesh"
boolean hasCapture = captured.hasCaptured();       // true

For a method called several times, newCapture(CaptureType.ALL) keeps every value, and getValues() returns them as a list.

6. Mock vs Nice Mock vs Strict Mock

EasyMock creates three kinds of mocks, and they differ in how they treat calls we did not record and the order of calls.

Mock typeCreated withUnexpected callCall order
Default mockmock() or createMock()Throws AssertionError at the callNot checked
Nice mockniceMock()Returns 0, false or nullNot checked
Strict mockstrictMock()Throws AssertionError at the callChecked
CustomerDao defaultDao = mock(CustomerDao.class);
replay(defaultDao);
Customer unexpected = defaultDao.save(new Customer("Anna"));   // AssertionError: Unexpected method call CustomerDao.save
CustomerDao niceDao = niceMock(CustomerDao.class);
replay(niceDao);
Customer nothing = niceDao.save(new Customer("Anna"));         // null
SequenceGenerator missed = mock(SequenceGenerator.class);
expect(missed.getNext()).andReturn(1L);
replay(missed);
verify(missed);                                                // AssertionError: Expectation failure on verify

Notice when each failure appears. An unexpected call throws an AssertionError inside the code under test, so a try/catch (Throwable) block in that code can swallow it. EasyMock also records such calls for verifyUnexpectedCalls(), which verify() includes, so the test still fails at verification in that case.

A strict mock checks the order of calls on one mock. To check the order across several mocks, create them from one strict control.

IMocksControl control = createStrictControl();
SequenceGenerator generator = control.mock(SequenceGenerator.class);
CustomerDao dao = control.mock(CustomerDao.class);
expect(dao.save(anyObject(Customer.class))).andReturn(null);
expect(generator.getNext()).andReturn(1L);
control.replay();
long outOfOrder = generator.getNext();             // AssertionError: Unexpected method call SequenceGenerator.getNext

Use strict mocks only when the order is part of the contract, such as opening a connection before writing to it. For most tests, default mocks give clearer failures and break less often on harmless refactorings.

7. Mocking Classes

EasyMock also mocks concrete classes, which helps with legacy code that has no interfaces. It generates a subclass with ByteBuddy and creates the instance with Objenesis, so the constructor of the class never runs.

class LoyaltyService {
    int pointsFor(Customer customer) {
        throw new UnsupportedOperationException("calls the loyalty platform");
    }

    final String tier(int points) {
        return points > 100 ? "gold" : "basic";
    }
}
class LoyaltyServiceMockTest {

    @Test
    void mocksConcreteClass() {
        LoyaltyService loyalty = mock(LoyaltyService.class);
        expect(loyalty.pointsFor(anyObject(Customer.class))).andReturn(150);
        replay(loyalty);

        assertEquals(150, loyalty.pointsFor(new Customer("Lokesh")));
        assertEquals("gold", loyalty.tier(150));
        verify(loyalty);
    }
}

The final method tier() runs its real code, because a subclass cannot override it. EasyMock cannot mock final classes, static methods or constructors at all, and a mocked class must not be in the default package. For static and final mocking, Mockito 5 with its inline mock maker is the usual choice, as shown in mocking static methods with Mockito.

8. EasyMock vs Mockito

Both libraries create mocks, but they ask us to write the test in a different order. EasyMock records expectations before the code runs, while Mockito stubs the answers first and verifies the calls afterwards, only for the calls we care about.

AspectEasyMock 5.7Mockito 5
Test styleRecord, replay, verifyStub, run, verify selected calls
Unexpected calls on a default mockFail the testReturn default values
Void methodsCall the method in record state and use expectLastCall()doThrow().when(mock).method()
Final classes and static methodsNot supportedSupported with the inline mock maker (the default)
JUnit 5 integrationEasyMockExtensionMockitoExtension

EasyMock suits teams that want every interaction declared up front, because a default mock fails on any call nobody recorded. Mockito tests are shorter for classes with many incidental calls, and Mockito has the larger user base, which is why most new projects pick it. A wider overview is in mock testing frameworks.

9. EasyMock FAQs

Void methods and the role of replay() confuse newcomers most, while teams with old EasyMock tests ask about its limits and about Mockito.

9.1. How do we mock a void method in EasyMock?

Call the void method on the mock in record state and use expectLastCall() to set the count or an exception, as in section 5. Without any expectLastCall() option, EasyMock expects one call that returns normally.

9.2. What does replay() do in EasyMock?

The replay() method switches the mock from record state to replay state. Before replay(), every call on the mock records an expectation, and after it, calls return the recorded results. Forgetting replay() is a common mistake, because the code under test records new expectations instead and gets null or 0 back.

9.3. Can EasyMock mock final classes or static methods?

No. EasyMock creates subclasses, so final classes, final methods, static methods and constructors stay real. Refactor the code to use an interface, or use Mockito 5, which supports static and final mocking.

9.4. Why does EasyMock fail with “Illegal type name” for a class mock?

Because the mocked class is in the default package. EasyMock 5.7.0 builds the mock class name from the package name, and an empty package leads to a name that starts with a dot. Put the class in a package, which every real project does anyway.

9.5. Should we use EasyMock or Mockito for a new project?

Mockito, unless the team already uses EasyMock. Both work with JUnit 5 and current Java versions, but Mockito supports static and final mocking and has more documentation and examples. EasyMock stays a good choice for keeping old tests running.

10. Conclusion

EasyMock tests follow four steps. We create mocks with mock(), record expected calls with expect() or expectLastCall(), switch to replay(), run the code under test and call verify(). The EasyMockExtension processes @Mock and @TestSubject in JUnit 5 and JUnit 6, and EasyMockSupport replays and verifies all mocks in one call.

Matchers must cover all arguments of a call or none, Capture gives access to the arguments, and the mock type decides how strict the test is. EasyMock 5.7 runs on current JDKs and mocks interfaces and non-final classes, while final classes and static methods need a refactoring or Mockito.

11. References

Happy Learning !!

Source Code on Github

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.