Why EasyMock Rocks
Listen to this article
My favourite mock objects library is EasyMock. IMHO, if you need much else for mocking stuff on a new project at least, you're mocking (read testing) the wrong thing.
It's uses Javas built-in dynamic proxies, meaning you can mock out anything that is defined by an interface.
I was helping someone to write some tests recently and explining how to use the library when she noted that "it's like a macro recorder." I had not thought of it like this but I guess she's right.
The idea is you create the mock object and then call it in exactly the way you expect the class under test to call it. The cool thing about this is that you get to use the same interface as the class under test. You setup expected method parameters, return values and even exceptions. Then when you're done setting it up, you call the class under test and EasyMock handles the rest.
As an example, I thought I'd take the clock code and write a test. (P.S. Anyone spot the "deliberate" mistake? Perryn Fowler did.)
public void testThreadInterruptedWithTimeExpiredShouldNotSleep() {
// Create the mock clock
MockControl clockControl = MockControl.createStrictControl(Clock.class);
Clock clock = (Clock) clockControl.getMock();
// Create the mock task
MockControl taskControl = MockControl.createStrictControl(Runnable.class);
Runnable task = (Runnable) taskControl.getMock();
// Setup an alarm to go off at time=10
Alarm alarm = new Alarm(task, 10, clock);
// When run, the alarm should check the current time.
// We'll tell it that the time=1
clock.getCurrentTimeMillis();
clockControl.setReturnValue(1);
// This will cause the alarm to calculate the sleep time.
// We pretend the thread was interrupted suffiently that the alarm
// time has now expired, time=11, which should cause the task to be run
// immediately.
clock.getCurrentTimeMillis();
clockControl.setReturnValue(11);
// We want the task to be run once, and once only!
task.run();
// Tell the controls we're ready to rock'n'roll
clockControl.replay();
taskControl.replay();
// And away we go...
alarm.run();
// Final check to ensure no expected methods are left outstanding
clockControl.verify();
taskControl.verify();
}
Running this test should demonstrate the bug just nicely, resulting in the following exception (that'll teach you to write the code without even running it!):
junit.framework.AssertionFailedError:
Unexpected method call sleep(-1):
sleep(-1): expected: 0, actual: 1
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:41)
at $Proxy0.sleep(Unknown Source)
at Alarm.waitForAlarmTime(Alarm.java:30)
...
How easy is that! You'll note that at least 50% of the "code" is actually comments I added for understanding, making it seem a lot longer than it really is.
Some traps for young players though:
- Don't forget to call replay() for each control prior to invoking the class under test. If you do forget, you'll end up java.lang.IllegalStateException: missing behavior definition for last method call on the mock;
- Don't forget to call setReturnValue() or setThrowable() on the control after calling a method on the mock object. Again you'll end up with java.lang.IllegalStateException: missing behavior definition for last method call on the mock; and;
- It's a good idea to call verify() on each control at the end of the test just to be sure.
About the only feature I can think of that I'd like added would be for a single control to handle multiple interfaces. That way I can have the ordering of calls across multiple interfaces checked as well. It should be relatively simple as dynamic proxies already support this. Without this feature, there's no real way to tell if the tasks run() method was called at the correct point in time. I draw the line at creating a dummy interface that extends both Clock and Runnable purely for testing ;-).
Comments
I totally agree on easy mock,it rocks. I'd like to see the ability to mock classes added. Vincent Massol (cactus guy) has a post with the url to a patch jar that allows you to do just that.
http://blogs.codehaus.org/people/vmassol/archives/000250.html
It suffers from allowing bad code to persist but when you are trying to get tests in place around a gig of nasty legacy code it sure comes in handy to not refactor the world all in one day.
BTW: I'd like to buy a fish license for my pet fish Eric :-)
Posted by: Bill Dudney | December 11, 2003 02:28 PM
Be careful not to make your tests too brittle. If you don't care about the order of mocked calls or the exact values of parameters, you should not check that in the expectations of your mock objects. If you do, unrelated changes to your code will cause a cascade of test failures. Eventually the overhead of testing will become unmanageable.
Posted by: Anonymous | December 11, 2003 05:46 PM
Agree though I have to ask why there would be failures due to unrelated changes. The ideal unit test (and I'm really speaking about unit tests this time hehe) attempts to minimise those kind of dependencies. Again, I agree with not making the tests too brittle. Sometimes you really only care that a given method was called, not necessarily in what order. I must admit I've rarely seen this kind of unit test but I'd be really keen (if you could be bothered with of course) to see an example. - Cheers, Simon.
Posted by: Simon Harris | December 11, 2003 06:00 PM
I would say that *most* code does not really care about the order of calls. For example, suppose the object being tested queries some properties of another object. Do you care what order it queries those properties? No, changing the order does not affect the behaviour being tested. Do you care how many times a property is queries? No, that's an implementation detail of the object being tested: it might cache returned values in local variables or might not, depending on what makes the code neater. So, a mock library should make it easy to ignore the order or number of calls. It should also make tests on the order or number of calls extremely explicit in your tests because that information is important documentation about your object's behaviour. If it doesn't, tests are brittle. In the property example, changing the order in which an object queries properties or whether it caches values in temporary variables will break your test even if it doesn't change the desired behaviour of the object. EasyMock makes it too hard to avoid brittle tests in my opinion.
Posted by: Anonymous | December 14, 2003 07:08 AM
Hmm..interesting. I must admit that I hand-code mocks when they are simple such as the example you've just given. Ie. when I just have essentially a datasource. So yup I agree. You have a good point. EasyMock does allow you to ignore the order of calls also. In fact that's the way I've seen most people us it. I guess I hilighted this feature because it was exactly what I needed for this example. I also tend to do lots of push-style rather than pull-style stuff. Sources and sinks or as James Ross likes to call them, relays. With a good combination of hand-coded tests and EasyMock usage, I've personally not found my tests to be brittle at all. -- Cheers, Simon.
Posted by: Simon Harris | December 15, 2003 06:11 AM