Saturday, April 30, 2011

EasyMock Issue #1: Missing Behavior Definition

I am planning to do a series of posts (including examples) on issues that people (myself included) run into while using EasyMock. The first of these involves EasyMock throwing an IllegalStateException "missing behavior definition for the preceding method call". The executive summary is that this is caused by the omission of a return value specification (andReturn(...)). If that is all you need to know, thanks for coming. If you would like to see a specific example, keep reading.

Let's say we are writing unit tests for a CustomerController class that uses an OrderService class to post a Customer's Orders:

// OrderService.java
public class OrderService {
  public int postOrders(List<Order> orders) {
    return orders.size();
  }
}

// CustomerController.java
public class CustomerController {
  private OrderService orderService;

  public CustomerController(OrderService orderService) {
    this.orderService = orderService;
  }
  
  public void postOrders(Integer...orderIds) {
    List<Order> orderList = new ArrayList<Order>();
    for (int orderId : orderIds) {
      orderList.add(new Order(orderId));
    }
    orderService.postOrders(orderList);
  }
}

In the real world, OrderService probably connects to external resources and therefore we want to mock it when creating a unit test for CustomerController. The interaction between CustomerController and OrderService consists of a single call to the OrderService.postOrders(List) method. Simple enough, here is our first attempt at writing this test:

public class TestCustomerController {
  @Test
  public void testPostOrders() {
    OrderService mockOrderService = EasyMock.createMock(OrderService.class);
    CustomerController controller = new CustomerController(mockOrderService);
    List<Order> expectedOrders = new ArrayList<Order>();
    expectedOrders.add(new Order(1));
    
    // You may do the following assuming postOrders returns void:
    mockOrderService.postOrders(expectedOrders);
    EasyMock.expectLastCall();
    
    // But unfortunately it doesn't.
    EasyMock.replay(mockOrderService);
    controller.postOrders(1);
  }
}

When you run this, it blows up with the following exception:
java.lang.IllegalStateException: missing behavior definition for the preceding method call postOrders([Order@82c01f])
 at org.easymock.internal.MocksControl.replay(MocksControl.java:174)
 at org.easymock.EasyMock.replay(EasyMock.java:1970)
 at TestCustomerController.testPostOrders(TestCustomerController.java:22)
Since I gave away the punch line in the opening paragraph of this post, you know that this happens because OrderService.postOrders() is not a void method; rather, it returns an int. I would argue that this is a pretty easy mistake to make, though, because the implementation of CustomerController does not care about or capture this return value. So unless we're paying pretty close attention to the OrderService interface, we might assume it returns void. And unfortunately, EasyMock requires that ALL non-void method calls on mocks have return values specified, even if the class under test will ignore it.

So the correct way to set up the OrderService.postOrders() expectation is as follows:

EasyMock.expect(mockOrderService.postOrders(expectedOrders)).andReturn(1);

In my next "EasyMock Issues" post, I will talk about a couple of things to look for when your expectations are mysteriously failing to match.

1 comment:

Martijn Dirkse said...

Dear Scott,

Thank you for your post. It comes up in my google search results if I look for EasyMock and "missing behavior definition". I got the missing behavior definition exception you describe here, but it had a different cause. It took me a long time to debug my problem, and therefore I would like to share my solution with your readers.

I have a test case involving many mocks and I use EasyMockSupport to replay and verify all my mocks at once. My test cases have a member variable support of type EasyMockSupport, and my test methods include the call support.replayAll(). Then in the tear down method I have support.verifyAll().

However, I created my mocks like someMock = EasyMock.createStrictMock(Xxx.class). This is wrong. It should be support.createStrictMock(Xxx.class). You should have the EasyMockSupport instance create your mocks if you use the EasyMockSupport instance to manipulate them.

With kind regards,

Martijn Dirkse
mhdirkse@live.nl