1. About Me
  2. Links
    1. Home
    2. Archives
  3. Sites I Dig
    1. Twitter
    2. GitHub
    3. Agile Alliance
    4. Agile Iowa

Easy(ier) Mock

We use EasyMock as our mocking framework and it's pretty easy. You create a mock, set some expecations, put the mock into replay state, and exercise the class under test. Once you've exercised the code, you can verify all of the expectations you set. (For a complete description, see the EasyMock documentation) A typical test may look like this:
public class StoreTest extends TestCase {
  @Test
  public void testThatCheckoutSendsPaymentInfoToProcessor() {
    CreditCardProcessor processor = EasyMock.createMock( CreditCardProcessor.class );
  
    PaymentInfo pi = new CreditCardPayment( "1234567890123456", "01/2008", 25.50 );

    EasyMock.expect( processor.handlePayment( pi )  ).andReturn( "confirmation-code" );

    EasyMock.replay( processor );

    Store store = new Store();
    store.setCreditCardProcessor( processor );
    store.checkout( pi );

    EasyMock.verify( processor );
  }
}
Pretty straightforward, but it's a bit wordy. The first thing we did is use a static import to take care of the requirement to fully qualify the easymock methods. Our test now looks like this:
public class StoreTest extends TestCase {
  @Test
  public void testThatCheckoutSendsPaymentInfoToProcessor() {
    CreditCardProcessor processor = createMock( CreditCardProcessor.class );
  
    PaymentInfo pi = new CreditCardPayment( "1234567890123456", "01/2008", 25.50 );

    expect( processor.handlePayment( pi )  ).andReturn( "confirmation-code" );

    replay( processor );

    Store store = new Store();
    store.setCreditCardProcessor( processor );
    store.checkout( pi );

    verify( processor );
  }
}
That's a bit better (less typing), but the next issue came into the picture when we added additional mocks.
public class StoreTest extends TestCase {
  @Test
  public void testThatCheckoutSendsPaymentInfoToProcessor() {
    double orderTotal = 25.50
    double receivablesBalance = orderTotal;
    CreditCardProcessor processor = createMock( CreditCardProcessor.class );
    AccountsReceivable receivables = createMock( AccountsReceivable.class );
  
    Order o = new Order( "123456",orderTotal );
    PaymentInfo pi = new CreditCardPayment( "1234567890123456", "01/2008", o );

    expect( processor.handlePayment( pi )  ).andReturn( "confirmation-code" );
    expect( receivables.add( o )  ).andReturn( receivablesBalance );

    replay( processor, receivables );

    Store store = new Store();
    store.setCreditCardProcessor( processor );
    store.setAccountsReceivable( receivables );
    store.checkout( pi );

    verify( processor, receivables );
  }
}
The problem with the above code is that it's easy to forget to replay and verify the new mock, it's three steps instead of one. To resolve this, we turned to Java 5 generics and a new base class:
public class TestCaseWithMocks {
	private List<Object> mocksUsed;
	
	@Before
	protected void createMockList() {
		mocksUsed = new ArrayList<Object>();
	}
	
	protected <T> T mock(Class<T> target) {
		T mock = EasyMock.createMock(target);
		mocksUsed.add( mock );
		return mock;
	}
	
	protected void replay() {
		EasyMock.replay( mocksUsed.toArray() );
	}
	
	protected void verify() {
		EasyMock.verify( mocksUsed.toArray() );
	}
}
Our base class now creates the mocks for us and keeps track of the mocks created by our test. Then, when verifying/replaying the mocks, it will verify/replay them all. Our test case now looks like the following:
public class StoreTest extends TestCase {
  @Test
  public void testThatCheckoutSendsPaymentInfoToProcessor() {
    double orderTotal = 25.50
    double receivablesBalance = orderTotal;
    CreditCardProcessor processor = mock( CreditCardProcessor.class );
    AccountsReceivable receivables = mock( AccountsReceivable.class );
  
    Order o = new Order( "123456",orderTotal );
    PaymentInfo pi = new CreditCardPayment( "1234567890123456", "01/2008", o );

    expect( processor.handlePayment( pi )  ).andReturn( "confirmation-code" );
    expect( receivables.add( o )  ).andReturn( receivablesBalance );

    replay();

    Store store = new Store();
    store.setCreditCardProcessor( processor );
    store.setAccountsReceivable( receivables );
    store.checkout( pi );

    verify();
  }
}
Now, when we add a new mock, we add it in one place, the replay and verify take care of themselves.