Thursday, September 21, 2006

Please mock me

Lately I've been working on a new line of code at work and have had the opportunity to introduce a few new technologies. Most recently I've been playing around with mock objects and the jMock library. So far I'm quite impressed with how flexible the library is. If you haven't already you owe it to yourself to take some time out to investigate mock objects.

For me, I'm most intrigued with how much more concise my tests feel. I feel like I'm writing more tests in a shorter amount of time while at the same time testing my code better than ever before. I attribute all of this to a change in test philosophy that jMock has given me.

If you've never used mock objects before the principle is simple, you use the mock object library to specify the behavior you want certain objects to emulate. You then use these mock objects in your test case to interact with the code you're trying to test. When your test case is done, the mock object library revisits the expectations you had of the interactions between the mock object and the code under test. If the expectations you defined weren't met, then the test fails. If they were, then the test passes. It's as simple as that.

For example, suppose we had a Model that we've defined that allows users to register a series of listeners that are notified when changes to the underlying model occur. The listener interface might look something like:

public interface ModelListener
{
void onAdd(String name, int value);
void onChange(String name, int oldValue, int newValue);
void onRemove(String name, int oldValue);
}

A test case which uses this code may then look something like this:

Mock mock = mock(ModelListener.class);
mock.expects(once()).method("onAdd").with(eq("a"), eq(1));
mock.expects(once()).method("onChange").with(eq("a"), eq(1), eq(2));
mock.expects(once()).method("onRemove").with(eq("a"), eq(2));

Model model = new Model((ModelListener) mock.proxy());
model.add("a", 1);
model.change("a", 2);
model.remove("a");

Pretty neat.