January 23, 2004

Missing the point of Mocks?

Triggered by Crazy Bob, Aslak and Simon Brown responded on whether it's better to create your own mocks with the IDE or use one of the dynamic mock packages, such as JMock. I think Bob and Simon have missed something...

One of the hard-earned lessons we learned from working on the original Mock Objects package was the importance of having good failure messages. It's worth spending the time and being verbose so that when an error does occur, you look at it and see the problem immediately. It's so annoying that most of the examples and textbooks don't bother to fill in the optional message parameter.

Prepackaged Expectations

The mockobjects package includes a set of expectation classes that check values, lists, sets, and so forth. They also implement Verifiable so instances will be picked up by the Verifier.verifyObject() method. Most people have forgotten, but our original idea was to expose the expectations in a mock class so we could address them directly, for example:

public class MockCheeseAction implements CheeseAction {
  public ExpectationValue cheeseName = new ExpectationValue("cheeseName");

  public void perform(String actualCheeseName) {
    cheeseName.setActual(actualCheeseName);
  }
}

public class CheeseCallerTest extends TestCase {
  public void testCheeseCalling() {
    MockCheeseAction mockAction = new MockCheeseAction();
    
    mockAction.cheeseName.setExpected("saint marcellin");
    
    new CheeseCaller("saint marcellin").call(mockAction);
    
    Verifier.verifyObject(mockAction);
  }
}

This will handle all the cases, including the method not being called, with a self-descriptive error message.

If you don't want to, or can't, use one of the dynamic mock libraries, then I agree, you can generate the stub and ignore the bits you don't need (I usually tweak the IDE to have the generated methods fail so that I don't get tests passing by accident). With anonymous classes, it's an obvious design.

public void testCheeseCalling() {
  final ExpectationValue cheeseName = new ExpectationValue("cheeseName");

  cheeseName.setExpected("saint marcellin");
    
  new CheeseCaller("saint marcellin").call(
    new CheeseAction() {
      public void perform(String actualCheeseName) {
        cheeseName.setActual(actualCheeseName);
      }
    });
    
  cheeseName.verify();
}

Crazy Bob codes this up by hand, but there are other options. The Expectation classes are also included in the nascent JMock library.

Lessons for framework developers

In working on the Mock Objects library we both succeeded and failed. I now think we shouldn't have gone so far in attempting to mock up the entire Java libraries. It's too much work and led us away from thinking about Mock Objects as a design rather than a testing technique. We also just didn't have the bandwidth to support it properly.

On the other hand, we have been quite good at layering our frameworks so you can get to the underlying behaviour that you need, especially in the dynamic libraries. We just haven't made it very obvious how. Best of all, at least people are still talking about us...

Posted by stevef at January 23, 2004 1:55 PM
Comments

Steve makes a valid point about descriptive error messages. Often times, unit tests are more for the person that will take over your code than you, and the more help you can give them, the better.

Posted by: Bob Lee at January 24, 2004 5:04 PM