EasyMock
Archived Posts from this Category
Archived Posts from this Category
I use EasyMock extensively to create mock objects for unit testing. The behavior verification is great for thoroughly testing an object’s interactions with other objects, known as collaborators. Sometimes, however, a mock cannot do all that is needed. Martin Fowler, in his article “Mocks aren’t Stubs”, identifies injected collaborators, or “test doubles”, as being mocks or stubs. Using EasyMock, it is possible to create an object which is both.
I recently ran into a scenario where I needed such a hybrid. Consider save method the following Struts action. This method handles both new and existing products, differentiated by the nullity of the id property of product.
Here is the class under test:
public class CustomerEditAction extends ActionSupport implements Preparable {
private CustomerDao customerDao;
private Long id;
private Customer customer;
public void setCustomerDao(CustomerDao customerDao) {
this.customerDao = customerDao;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
public void prepare() throws Exception {
/* Sets up the id and the customer. Removed for brevity */
}
public String save() {
if (customer.getId() == null) {
customerDao.insert(customer);
setId(customer.getId());
} else {
customerDao.update(customer);
}
return Action.SUCCESS;
}
And here is the collaborating dao:
public interface CustomerDao {
List getAll();
Customer getForId(Long id);
void insert(Customer customer);
void update(Customer customer);
}
Testing the “update” case of the save method where product.getId() is not null with a mock object is simple, all we need to verify that the update method of product dao is called with the right argument.
The “insert” case is not as straightforward, and is a case where a simple mock is not sufficient.
This issue is with verifying the additional step of setting the id of the product to the action after the insert is performed. It is assumed that the productDao itself modifies the id of the product after inserting it into the database. Since a mock object is just an empty shell which follow scripted behaviors without any implementation, there is no way to use one to actually modify the product as the actual dao would. But without actually modifying the product passed as an argument to productDao, there is no way to verify that the id property of the action is set with the value from the id property of the product. The alternative is to use a stub in our test case instead of a mock, like the following:
private static abstract class StubCustomerDao implements CustomerDao {
static final Long ID = 9999L;
public void insert(Customer customer) {
customer.setId(ID);
}
}
The class is actually an inner class of my test class. It is abstract for a reason - keep reading for the explanation.
The resulting unit test for the insert case would be:
public class CustomerEditActionTest {
private CustomerEditAction action;
private StubCustomerDao customerDao;
private Long id;
private Customer customer;
@Before
public void setUp() throws Exception {
action = new CustomerEditAction();
/*
* Here we are creating a mock of our stub, a private inner class
*/
customerDao = createMock(StubCustomerDao.class,
StubCustomerDao.class.getMethod("getForId", Long.class),
StubCustomerDao.class.getMethod("update", Customer.class));
id = 1L;
customer = new Customer();
customer.setId(id);
action.setCustomerDao(customerDao);
}
/* Some tests removed for brevity */
@Test
public void testSaveInsert() {
reset(customerDao);
customer.setId(null);
action.setCustomer(customer);
replay(customerDao);
assertSame("result not success", Action.SUCCESS, action.save());
assertEquals("customer id is equals", StubCustomerDao.ID,
action.getId());
verify(customerDao);
}
@Test
public void testSaveUpdate() {
reset(customerDao);
action.setCustomer(customer);
customerDao.update(customer);
replay(customerDao);
assertSame("result not success", Action.SUCCESS, action.save());
verify(customerDao);
}
/* Stub class */
private static abstract class StubCustomerDao implements CustomerDao {
static final Long ID = 9999L;
public void insert(Customer customer) {
customer.setId(ID);
}
}
}
Switching from a mock to a stub for the insert method means that I am exclusively verifying state, and no longer verifying behavior. Asserting that the product id and the action have the correct value in their id property gives me reasonable assurance that the action is doing what I want it to do.
But why an abstract class for ProductDaoStub? Because I only want to use a stub for this one test method. It still makes sense to use a mock for calls to to the other methods of the class, like update and getForId. This is where the class extension of EasyMock really shines. With the class extension MockControl, mocks can be created from concrete types, like HttpServletRequest, not just from interface types like ProductDao. By default, a class mock causes all methods to be mocked. You can, however, specify that only certain methods be mocked, and the rest of the class methods are invoked in the class itself. I am using this feature to mock my stub. This gives me all of the power of a mock and the ability to carve out a stub for the one method which needs it. This table shows the effect:
| Method | interface ProductDao | ProductDaoStub | Mock of ProductDaoStub | Net Effect |
|---|---|---|---|---|
| getAll | unimplemented | unimplemented | unimplemented | unimplemented |
| getForId | unimplemented | unimplemented | mocked | mocked |
| insert | unimplemented | stubbed | unimplemented | stubbed |
| update | unimplemented | unimplemented | mocked | stubbed |
The full code can be browsed here and checked out with the following command:
svn co http://bentomasini.com/svn/repo1/demos/mockstub/tags/TAG-mockingAStub mockstub
Update: Fixed a bug in the unit test where the customer’s id was being asserted and not the action’s id. That kinda misses the whole point.
0 comments Wednesday 05 Mar 2008 | btomasini | Software, Java, EasyMock, JUnit