EasyMock is a framework designed to make construction of mocks straightforward. Essentially, you
- Create a mock with EasyMock.
- Set the expectations on the calls to be made on that mock by making those same calls. At this point you can also set return values.
- Put the mock into replay mode
- Run your test.
- Verify the mock.
As an example,
Foo foo = EasyMock.createMock(Foo.class);
Bar myBar = new Bar();
EasyMock.expect(foo.someMethod(1, myBar, "baz")).andReturn(17L);
EasyMock.replay(foo);
ClassUnderTest underTest = new ClassUnderTest(foo);
long result = underTest.classUnderTest("baz", myBar);
assertEquals(17L, result);
EasyMock.verify(foo);
Before the replay call, the call to foo.someMethod() sets an expectation that someMethod should be called on foo with exactly the parameters 1, myBar, and “baz”. EasyMock will throw an exception if any other method is called, or if that method is called with different parameters. EasyMock.verify will throw an exception if any expectations have not been met.
EasyMock can be made more general than this. Let’s say that our class under test actually instantiates a Bar, and we want to all the call to someMethod no matter which Bar is passed in. We can do this, but it requires that we use EasyMock’s matchers rather than concrete values when setting up the expectation. Specifically, EasyMock.isA(Bar.class) matches any Bar. We can’t do
EasyMock.expect(foo.someMethod( //WRONG!
EasyMock.isA(Bar.class), "baz").andReturn(17L);
because it turns out that when setting expectations on an EasyMock mock, you can’t mix concrete values and matches. You need to use all one or all the other.
EasyMock.expect(foo.someMethod(
EasyMock.anyInt(),
EasyMock.isA(Bar.class),
EasyMock.eq("baz"))).andReturn(17L);
This expectation will allow any single call to someMethod which is passed any int as the first argument, any Bar as the second, and the string “baz” as the third. It further turns out that EasyMock provides a mechanism for anyone to create their own argument matchers. We could, for example, create an argument matcher that only matches instances of Bar which satisfy some more specific criterion.
Now, if you think about it, all these matchers — whether EasyMock’s or custom — need to return a value of the correct type to be passed to foo.someMethod. Who knows what constructor’s Bar might have, and what might happen if EasyMock tries to instantiate one? And how can the fake Bar that is returned from EasyMock.isA() and handed to foo.someMethod() have the information to do the correct matching operation?
The answer is, it can’t. EasyMock cheats. And that brings us to our point. When using argument matchers, EasyMock expects — nay, requires — that each matcher make exactly one call to EasyMock.reportMatcher(…). And how does it know which matcher goes with which argument? Well, it lines them up. By calling
foo.someMethod(EasyMock.anyInt(), EasyMock.isA(Bar.class),
EasyMock.eq("baz"));
each of the EasyMock matchers does a call to EasyMock.reportMatcher(), which squirrels away a specific matcher in a global list. When the mock foo gets a call to someMethod, it expects to find 3 matchers in that global list. (Well, either 3 or zero, zero being the case where we’re using concrete values and not matchers.) If it find 1 or 2, instead, it throws a cryptic exception, because you mixed concrete values and matchers, and it has no way to know which argument each of the matchers should go with. It simply can’t tell.
So … problem #1 rears its head when we use our IDE’s automated refactoring tools. Someone at some later time decides that they need to generalize Foo’s someMethod, so they use their IDE to add another argument, a boolean let’s say, and they tell their IDE the default value is false. Our call using matchers now looks like
foo.someMethod(EasyMock.anyInt(),
EasyMock.isA(Bar.class), EasyMock.eq("baz"), false)
That someone probably has no idea this test is here. When they finally run the full test suite, the cryptic error pops up, they come snooping around this code. If they’re not already familiar with EasyMock and this particular quirk, they’re in for a fun time, until they discover that they need to change this to
foo.someMethod(EasyMock.anyInt(), EasyMock.isA(Bar.class),
EasyMock.eq("baz"), EasyMock.anyBoolean())
The second problem is even more insidious. What happens when we refactor our test method. For whatever reason, let’s say we extract the Bar matcher into a local variable.
Bar bar = EasyMock.isA(Bar.class);
foo.someMethod(EasyMock.anyInt(), bar,
EasyMock.eq("baz"), EasyMock.anyBoolean());
And now EasyMock starts failing. Why? Because each argument matcher is responsible to call the global EasyMock.reportMatcher — in the correct order. By this refactoring, the reportMatcher calls are now out of order, and EasyMock is either going to have class cast exceptions, or the expections on someMethod that previously passed will now fail.
Or worse.
One way to avoid these problems is to decide not to use EasyMock at all, instead creating your own hand-rolled doubles. And if you decide to use EasyMock in your project, …
- Make sure at least someone on your team understands how EasyMock works under the hood.
- Be aware of EasyMock’s all-concrete or all-matchers requirement, and that violations of this requirement can be violated by automated refactoring.
- If you use EasyMock matchers, make sure that they are produced while marshalling the arguments to call the mock method, and not beforehand.