Sunday 4 February 2007

Complex expectations in EasyMock made easy using JSP EL

Setting up expectations in a mock framework like EasyMock is usually straightforward when dealing with simple types like integers or strings. But from time to time I run into situations when things become more complicated. Consider the following example.

We have a class Customer that has properties like name and address, but also a bunch of other things. We now want to test a method that populates the phone number field from some phone number directory. This test code could look something like this.


public void testPopulatePhoneNumber() throws Exception {
Services services = createMock(Services.class);
Customer loadedCustomer = new Customer();
loadedCustomer.setUserId("alb");
loadedCustomer.setFirstName("Al");
loadedCustomer.setLastName("Bundy");
// Set other properties
Customer saveCustomer = new Customer();
saveCustomer.setUserId("alb");
saveCustomer.setFirstName("Al");
saveCustomer.setLastName("Bundy");
saveCustomer.setPhoneNumber("555-SHOE");
// Set other properties
expect(services.loadCustomer("alb")).
andReturn(loadedCustomer);
expect(services.lookupPhoneNumber("Al", "Bundy")).
andReturn("555-SHOE");
services.saveCustomer(saveCustomer);
replay(services);

CustomerManager customerManager = new CustomerManager();
customerManager.populatePhoneNumber("alb");
verify(services);
}


(For simplicity I have a single interface for all sorts of good stuff, which of course wouldn't happen in a real system. Don't start building your own enterprise CRM system based on this. :-) )

The problem here is how you verify that the correct customer object is being passed into saveCustomer(). In this example EasyMock would use the equals() method of the Customer class to compare the expected object with the actual object. This means that to make all sorts of test cases work you would have to make the equals() method compare every single field in the class. Maybe this is not what you want. Maybe you want two customer objects to be considered equal if they have the same user id. But in this way the test cases have sort of hi-jacked the equals() method. Also you have to set up a lot of properties in the test cases and you get big equals() and hashCode() methods.

So I came up with the idea that you could use JSP EL (expression language) to write expressions that will make an assertion that the object is as expected. Writing an argument matcher for EasyMock that does this turned out to be pretty easy. Using this the expectation for saveCustomer() would become like this:


services.saveCustomer(assertBean(
"${bean.phoneNumber == '555-SHOE'}",
Customer.class));


Also you don't need the saveCustomer object any longer and the only properties that you have to set in the test case are those that actually has something to do with the test.

A small problem with this approoch though is that refactorings of the objects that are referenced within the EL expression will break the tests.

I have put up a small web page with the source code if you want to try it out at http://www.nilin.se/java/beanassert/.

No comments: