Monday, August 11, 2008

EasyMock Tip

EasyMock is a light weight Mock library used for mocking interfaces in Unit Tests. Here is a little tip on what I believe to be an undocumented best practice.

Let's say you have a Unit Test that is testing an object called CustomerController. CustomerController has a dependency on Interface CustomerDao Typically, you may see in the unit test, code that looks similar to the following:

protected void setUp() throws Exception {
    controller = new CustomerController();
    custDao = createMock(CustomerDao.class);
    controller.setCustDao(custDao);
}

public void testFindCustomers() throws Exception {
    expect(custDao.getCustomers()).andReturn(new ArrayList());
    replay(custDao);
    controller.findCustomers();
    verify(custDao);
}

As you will notice, I am specifying one mock custDao, and the system under test is the controller. This is pretty standard and straightforward use of EasyMock. Now let's see what happens when we have to add an additional dependency on the controller:

protected void setUp() throws Exception {
    controller = new CustomerController();
    custDao = createMock(CustomerDao.class);
    invoiceController = createMock(InvoiceController.class);
    controller.setCustDao(custDao);
    controller.setInvoiceController(invoiceController);
}

public void testFindCustomers() throws Exception {
    expect(custDao.getCustomers()).andReturn(new ArrayList());
    replay(custDao);
    controller.findCustomers();
    verify(custDao);
}

public void testApplyCustomerPayment() throws Exception {
    Customer customer = new Customer();
    BigDecimal paymentAmt = new BigDecimal(100);
    invoiceController.applyPayment(customer, paymentAmt);
    replay(invoiceController);
    controller.applyPayment(customer, paymentAmt);
    verify(invoiceController);
}

This also seems to be a very valid test. I would argue though that it is not complete. You are verifying that the appropriate methods are called on the objects / interfaces that you set expectations for. However, if a method is called on the custDao in the controller.applyPayment or a method is called on the invoiceController during the controller.findCustomers, without an expectation / verification the test will still pass. This may not be the desired behavior and therefore I would argue that the test is incomplete.

On my team, someone determined that a way to handle these types of situations is to create a private method on the test called replayAll and verifyAll. This included the maintaining of an array list of all of the mocks that is used within the test. While this is a valid solution, I wanted to see what other EasyMock users were doing. Here is what I found:

protected void setUp() throws Exception {
    controller = new CustomerController();
    mockControl = createControl();
    custDao = mockControl.createMock(CustomerDao.class);
    invoiceController= mockControl.createMock(InvoiceController.class);
    controller.setCustDao(custDao);
    controller.setInvoiceController(invoiceController);
}

public void testFindCustomers() throws Exception {
    expect(custDao.getCustomers()).andReturn(new ArrayList());
    mockControl.replay();
    controller.findCustomers();
    mockControl.verify();
}

public void testApplyCustomerPayment() throws Exception {
    Customer customer = new Customer();
    BigDecimal paymentAmt = new BigDecimal(100);
    invoiceController.applyPayment(customer, paymentAmt);
    mockControl.replay();
    controller.applyPayment(customer, paymentAmt);
    mockControl.verify();
}

The subtle difference to look at between these two scenarios is that the latest one uses what is known as a IMocksControl object. This IMocksControl object keeps track of what mocks were created on it so that all you would need to do in order to make sure that you are replaying and verifying all of the mocks is to call the replay and verify methods on the IMocksControl object. This ensures that all of the expectations, whether explicit or implied, are met. Thanks Tammo Freese for your help in finding this great golden nugget within the EasyMock framework.

1 comment:

freakyTechie said...

I liked the post very much. A developer on my team was actually struggling to use EasyMock (of course, he's a first timer) and your entry saved him quite a bit of time understanding the subtle difference between direct invocation of createMock and creating control and then, calling createMock on the control object.
Cheers