Tuesday, February 13, 2007

EasyMock - Pros, Cons, and Best Practices

Last fall the EasyMock jar file was dropped into my code base so that we could start writing JUnit tests with it. EasyMock documentation is online if you want to know more.

The good news is that mocking out interfaces is very, very easy. I like the “Humane Interface” that it offers; always returning this from methods so that method chaining is easy.

EasyMock.expect(MyObject.message()).andReturn(true).once();

This makes writing EasyMock code much easier. I find EasyMock a great tool for mocking out interfaces where the System Under Test's (SUT) success/failure criteria is not dependent on the method calls to the collaborator (indirect output). If your mock objects are having get* methods invoked and you’re using JUnit asserts afterwards to assert proper state within the SUT, then you’re in EasyMock’s sweet spot. Also, don’t be afraid to write ArgumentMatchers. The first one is tough to write, but they are all basically the same after that. EasyMock has definitely sped up the writing of many of my unit tests.

I do have a list of complaints about EasyMock, though.

Only Works on Interfaces - EasyMock does not mock out classes, only interfaces. This has lead to an explosion of interfaces in my code base. The interfaces only exist as a means to using EasyMock. So test details are dictating what the production code looks like, and not in a good way. I call the proliferation of classes named *Impl the Interface/Impl Anti-Pattern. Four things can be done about this:

  1. Live with it and call it “test driven design”
  2. Upgrade to JUnit 4.0 and upgrade to the version of EasyMock that mocks classes
  3. Use Groovy script to mock classes
  4. Make collaborating classes non-final and write a test specific subclass

“Humane Interface” - The method chaining style interface is easy to write, but I find it difficult to read. When a test other than the one I’m working on fails, it’s often very difficult to determine what exactly is going on. I end up having to examine the production code and the test expectation code to diagnose the issue. Hand-rolled mock objects are much easier to diagnose when something breaks. Also, setting expectations for void methods is done differently than non-void methods. This results in code that mixes a humane interface with a classic API interface… which means my brain must process both styles at once, which is more difficult than either one alone. This problem is especially nasty after refactoring expectation code to reduce duplication. For the life of me, I cannot follow expectation code that has been refactored into shared methods. I now allow small amounts of duplicate expectation code to dwell in my unit tests. I need to see all the expectations at once, in one method, or I have no hope of understanding it.

Abstract Test Cases - Managing EasyMock within abstract test cases has proven to be very difficult. Managing replay and record states leads to a confusing mess. At this point I’ve given up mixing EasyMock and abstract TestCase objects. When something breaks it simply takes too long to diagnose. An alternative is to create custom assertion methods that can be reused. Beyond that, I've given up on Abstract TestCase objects anyway, on the grounds of preferring composition of inheritance.

Don’t Replace Asserts with Verify - The easiest methods to understand and test are methods that perform some sort of work. You run the method and then use asserts to make sure everything worked. In contrast, EasyMock makes it easy to test delegation, which is when some object other than the SUT is doing work. Delegation means the method’s purpose is to produce a side-effect, not actually perform work. Side-effect code is sometimes needed, but often more difficult to understand and debug. In fact, some languages don’t even allow it! If you’re test code contains assert methods then you have a good test. If you’re code doesn’t contain asserts, and instead contains a long list of EasyMock.verify() calls, then you’re relying on side effects. This is a unit-test bad smell, especially if there are several objects than need to be verified. Verifying several objects at the end of a unit test is like saying, “My test method needs to do several things: x, y, and z.” The charter and responsibility of the method is no longer clear. This is a candidate for refactoring.

All or Nothing Testing - With hand-rolled mock objects it is easy to target just a critical section of a method and ignore other parts. Maybe all I really care about testing is the first 4 lines of code and not the last 4. Once you use an EasyMock object you no longer get this flexibility. EasyMock objects are verified in entirety… there is no way to make it a strict mock for one method call and nice for all the others. You can’t verify like that either. I end up littering the test code with expectation-setting calls that I truly don’t care about. EasyMock could fix this by having NiceMocks try not to return null. For instance, if a NiceMock string returning method is called, then just have the NiceMock return “” instead of null. Use reflection to invoke some sort of default constructor on the return type if available. This would make NiceMocks a lot more useful.

Create/Replay Code Duplication - Since NiceMocks aren’t really that nice to work with (see aove item), I see the same 3 lines to create and replay an object littered across many test cases. At some point the cost of repeating this code outweighs what it would take to just hand-roll a nicely behaved mock object. My lesson learned is not to forget how to write your own mocks. EasyMock is not a replacement for all mock objects, and should be used only when it would reduce the amount of code written.

Error Messages - Is there a free framework that includes readable error messages? Every user who starts out gets confused by the message ‘Expected “Object X” but received “Object X”‘. It’s a lesson in object equality and toString for each new user. The error message relies on the toString() method while the comparison uses reference comparison. Be Warned.

While the list of complaints is longer than the list of accolades, EasyMock has made unit testing a lot easier. After using it a few months, I’ve reflected on the experience and create a list of Best Practices.

Easy Mock Best Practices

  1. Learn to use EasyMock and ArgumentMatchers. The learning curve is steep but over quickly.
  2. Upgrade to the newest version so you can mock out classes. Beware of creating interfaces simply for testing.
  3. Beware the Abstract TestCase. What makes sense to you at the time will confuse almost everyone else!
  4. EasyMock.verify() is a bad smell. Ask yourself if you really need it.
  5. Don’t throw out hand-rolled mock objects. They have their place.

Thoughts anyone?

No comments: