Introduction
EasyMock is a mock library that can be used as a supplemental tool for a testing framework, such as TestNG, to improve unit testing implementations. EasyMock provides the means (through the use of mock objects) to specify the response that will be returned when the code being tested makes calls to external methods. This will allow the result of the unit test to be isolated from variables outside of the scope of the unit test code.Refer to the official EasyMock documentation for more detailed explanation of the tool (latest as of this writing EasyMock3.0.Docs).
Note: For the sake of simplicity, not all the details of the code examples have been included.
What is a mock?
Short concise description:
Mocking and Mock Objects is not specific to Java. Mock objects is a unit testing technique in which a code chunk is replaced by dummy implementations that emulate real code. This helps one to write unit tests targeting the functionality provided by the class under test. - referenceFor more information, this article offers very good introduction to the concept of mocking:
Mock Objects.
Stubs vs. Mocks
An alternative to using a mock object is to create a stub object; a lightweight implementation of the interface for the external methods, that just return the expected response. These two approaches are best illustrated with an example.Let us say you want to write a unit test for an object
Foo
that does something cool, doSomethingCool()
, and uses another object, Emailer
, to send emails while doing something cool as follows:
package com.partnet.foo; public class Foo { private Emailer emailer; public Foo(Emailer emailer) { this.emailer = emailer; } public void doSomethingCool() { // something worthy of being unit tested emailer.sendEmail(...); } }
package com.partnet.foo; public interface Emailer { boolean sendEmail(...) throw EmailerException; }If you're trying to test the
doSomethingCool()
method, you don't want to, shouldn't have to, and thankfully don't need to worry about what emailer.sendEmail()
does (assuming the implementation of the call has no impact on the code you're testing of course).The assumption here is that the real implementation of
Emailer
, say it's called FancyEmailerImpl
, is a non-simplistic object that may require some configuration or won't work in a non-real environment. In this case you do not want to have to create a real FancyEmailerImpl
and use it in your testing because again, what it does has no bearing on the code you're actually trying to test.Stub Objects
Using the stub object approach, you could create a stubbed-out/dummy version ofEmailer
like the following:
// stub object package com.partnet.foo; public StubEmailer implements Emailer { public boolean sendEmail(...) throws EmailerException { return true; } }Simple, easy, done... right? Yep, you can now just setup your unit test as follows and test away:
// stub object use package com.partnet.foo; public TestFoo { @Test public void test() { Emailer stubEmailer = new StubEmailer(); Foo myFoo = new Foo(stubEmailer); myFoo.doSomethingCool(); } }The downside to this approach is that you end up building and maintaining a bunch of stub code. Additionally, stub code can be difficult to write as you get into stubbing out more complex objects that you want to test around. For example, if the code you're testing sets an ID on your
Emailer
and later retrieves it via setId()/getId()
methods, this behavior would need to be stubbed out correctly.
Mock Objects
The alternative to writing stubs is writing mocks, or more correctly, using a mock object library to create mock objects for you. EasyMock is such a library. The following demonstrates how to use EasyMock to mock out theEmailer
from above.
// mock object example package com.partnet.foo; public TestFoo { @Test public void test() { Emailer mockEmailer = EasyMock.createMock(Emailer.class); Foo myFoo = new Foo(mockEmailer); myFoo.doSomethingCool(); } }Simple, easy, no stub code to maintain. Even cooler is how you solve the problem mentioned above regarding
setId()/getId()
. This is where the concept of Expectations comes in (in terms of EasyMock, these are called IExpectationSetters
). An Expectation is basically you telling a mock object how to behave and what you expect to happen when you run the unit test code: "When this happens, do this", "When your getId()
method is called, return this value", "I expect that your setId()
method will be called". After your unit test code has run, you can then ask EasyMock to verify that everything you expected to happen actually occurred so that you can be assured your code does what you think it does.
Downloads
The latest EasyMock files can be downloaded from a link on the EasyMock Download page.Integration
Add these to the classpath:EasyMock only works with Java 1.5.0 and above.
Writing Tests
Basic steps for EasyMock use
- import EasyMock
- import static org.easymock.EasyMock.*; // non-static imports may also be used
- create mock object
- private ClassOrInterface mock;
- mock = createMock(ClassOrInterface.class); // throws AssertionErrors for all unexpected method calls
- mock = createNiceMock(ClassOrInterface.class); // allows all method calls and returns appropriate empty values (0, null or false)
- specify expectations for mock (in other words, "I'm going to tell you what you should expect to do")
- mock.doThis();
- mock.doThat();
- specify that the last method could be called x times
- expectLastCall().once(); // default one call
- expectLastCall().times(3); // exact calls
- expectLastCall().times(2, 5); // min, max calls
- expectLastCall().atLeastOnce(); // one or more times
- expectLastCall().anyTimes(); // 0 or more times
- specify method response or return value
- expect(mock.doThis()).andReturn(returnValue, i.e. "true");
- make mock available for use (in other words, "I'm done telling you what you should expect to do, now get ready to do it.")
- replay(mock)
- run unit test that will call doThis() and doThat()
- verify proper response
- assertTrue(doThis());
- verify that expected behavior was called (did everything get called the expected number of times?)
- verify(mock)
Tips
Prefer Nice Mocks
There are basically 3 different ways to have EasyMock create a mock object for you.createMock(Foo.class)
createNiceMock(Foo.class)
createStrictMock(Foo.class)
createNiceMock()
. Nice mocks are, as their name suggests, 'nice'. 'Forgiving' is probably a more appropriate term in that, as the API states, nice mocks will return 0
, false
, or null
for unexpected invocations. Meaning:- for methods that return
int
,double
,long
,char
,float
,byte
,short
(non\-boolean
primitives) - the returned value is0
- for methods that return
boolean
(not sure aboutBoolean
) - the returned value isfalse
- for methods that return
Object
(or anything that extendsObject
) - the returned value isnull
getId()
method. Chance are you don't care what getId()
does and if the code you're testing doesn't care what it does, but happens to call it in a log statement or something that is irrelevant to your test, then a nice mock will allow you to not have to setup an expectation for that method (see Example 1).
package com.partnet.foo; public class Foo { private Emailer emailer; public void doSomethingCool() { // I don't care about this and don't want to setup an expectation for it log.debug("emailerId:" + emailer.getId()); // something worthy of being unit tested emailer.sendEmail(...); } }Additionally, if another developer were to remove that log statement, the unit test wouldn't have to change because the expectation was never added and the tests wouldn't break just because a log statement was removed.
Prefer "Relaxed" Expectations
The term "relaxed" refers to theIExpectationSetters
API as it relates to configuring the number of times an expectation is valid. Specifically, the methods once()
, times(count)
, and times(min, max)
should be avoided unless you really care and/or your test relies on them. The methods anyTimes()
and atLeastOnce()
should be used instead. These "execution count" methods set the number of times a given method is expected to be called. The reason the latter methods should be preferred is that, while setting up explicit method call counts is certainly valid, implementations change over time and often times those changes have no effects on the functionality of a given object. For example, who cares how many times emailer.getId()
is called in the above example and does the fact that it gets called multiple times have any effect on the calling code? Clearly not. However, if an expectation like the following was added:
EasyMock.expect(mockEmailer.getId()).andReturn(1).once();and later the log statement was removed or an additional log statement was added that called
getId()
again, the unit test would fail.However, this practice is not an across-the-board standard as it makes sense to setup an expectation like the following:
EasyMock.expect(mockEmailer.sendEmail(...)).andReturn(true).once();because sending multiple emails is most likely not expected or okay.
NOTE: not specifying one of the "execution count" methods is the same as using the
once()
method (i.e. once()
is assumed unless otherwise instructed).
Stub Returns
andReturn() vs. andStubReturn()EasyMockSupport
Introduced in version 2.5.2,EasyMockSupport
is your friend. It saves you the trouble of having to call reset()
, replay()
, and verify()
on all your mock objects. As the javadoc shows, an example usage is as follows:
package com.partnet.foo; public class TestFoo extends EasyMockSupport { // this is key @Test public void test() { resetAll(); // reset all existing mocks // create mocks firstMock = createMock(A.class); secondMock = createMock(B.class); replayAll(); // put all mocks (new & existing) in replay mode // use mocks verifyAll(); // verify all mocks (new & existing) } }
Mock Builders
See [some future post] for how cool this feature is.Argument Matching
Argument matching is related to setting up expectations and is one of those subtleties of EasyMock that you might miss and certainly not understand until you really start writing tests.The following shows setting up an expectation without any argument matchers:
EasyMock.expect(mockEmailer.sendEmail("foodaddy")).andReturn(true);In this scenario, you're telling EasyMock to return
true
when sendEmail()
is called with "foodaddy"
as the parameter. Simple enough, but what if you wanted tell EasyMock to return true
for all parameter values, or only certain parameter values, or only on Tuesdays, etc? This is where argument matchers come to play.The following demonstrates an argument matcher that matches all String values:
EasyMock.expect(mockEmailer.sendEmail(EasyMock.isA(String.class))).andReturn(true);What about all String values except
"foodaddy"
?
EasyMock.expect(mockEmailer.sendEmail(
EasyMock.not(EasyMock.eq("foodaddy")))).andReturn(true);EasyMock supports a wide selection of argument matchers that can be used to create any type of matcher. Here are some of the matcher methods:
anyInt(), anyObject(), contains(), eq(), geq(), gt(), isA(), leq(), lt(), same(), startsWith()
.Argument matchers can be combined using the following methods:
and(), or(), not()
Consult the API for a complete listing, but as an example:
// true for "foodaddy" or "foo.*daddy" EasyMock.expect(mockEmailer.sendEmail( EasyMock.or(EasyMock.eq("foodaddy"), EasyMock.and(EasyMock.startsWith("foo"), EasyMock.endsWith("daddy"))) )).andReturn(true);EasyMock even has support for creating custom argument matchers. Consult the API for more info. For more info on argument matchers, see this blog post.
Argument Matching part 2
EasyMock has this annoying feature/limitation that prevents mixing non-argument matchers (raw values) with argument matchers when setting up expectations. I often encounter this when trying to setup expectations for a method that takes multiple parameters where some are simple primitives and others are complex types. This can be annoying because often times you want to be able to setup your expectations in this way. Given the following:public interface Emailer { boolean sendEmail(String emailAddress, String message); }
public class Foo { private Emailer emailer; public Foo(Emailer emailer) { this.emailer = emailer; } public void doSomethingCool(boolean useDefault, String emailAddress) { String message = null; if (useDefault) { message = "default message"; } else { message = "something else"; } emailer.sendEmail(emailAddress, message); } }So now you want to test this code so you start as follows:
public class TestFoo { @Test public void test_doSomethingCool() { final String emailAdrs = "foo@foodaddy.com"; Emailer mockEmailer = EasyMock.createMock(Emailer.class); // return true when emails are sent to foo@foodaddy.com // regardless of the message being sent EasyMock.expect(mockEmailer.sendEmail( emailAdrs, EasyMock.isA(String.class))).andReturn(true); EasyMock.replay(mockEmailer); Foo myFoo = new Foo(mockEmailer); myFoo.doSomethingCool(true, emailAdrs); EasyMock.verify(mockEmailer); } }As the comment above states, you don't care what message is being sent to "
foo@foodaddy.com
". You want all emails sent to "foo@foodaddy.com"
to return true
. Seems valid enough, but when you run your test, you'll get an error message similar to this:java.lang.IllegalStateException: 2 matchers expected, 1 recorded. This exception usually occurs when matchers are mixed with raw values when recording a method: You need to use no matcher at all or a matcher for every single param:
Huh? The key is "..._matchers are mixes with raw values_...". Unfortunately, you can't do what you were hoping to do. EasyMock requires that you use ALL raw values or ALL argument matchers in a given expectation. So the following shows how you accomplish this:
public class TestFoo { @Test public void test_doSomethingCool() { final String emailAdrs = "foo@foodaddy.com"; Emailer mockEmailer = EasyMock.createMock(Emailer.class); EasyMock.expect(mockEmailer.sendEmail( EasyMock.eq(emailAdrs), EasyMock.isA(String.class))).andReturn(true); EasyMock.replay(mockEmailer); Foo myFoo = new Foo(mockEmailer); myFoo.doSomethingCool(true, emailAdrs); EasyMock.verify(mockEmailer); } }The
eq()
argument matcher is what's needed here and is especially handy at matching primitive values. The eq()
argument matcher can certainly be used to match more complex types, but with primitives, you don't have to worry about the implementations of equals()
and hashCode()
as discussed next.
Captures
These are cool, but the documentation and examples I've found are not. I like examples so hopefully the following will demonstrateCapture
's coolness. Given the following:
public class Recipient { private String name; private String email; public Recipient(String name, String email) { this.name = name; this.email = email; } // getters for above attrs }
public interface Emailer { boolean sendEmail(Recipient r, String message); }
public class Foo { private Emailer emailer; public Foo(Emailer emailer) { this.emailer = emailer; } public void doSomethingCool(boolean useDefault) { Recipient r = null; if (useDefault) { r = new Recipient("foodaddy", "foo@part.net"); } else { r = new Recipient("noreply", "noone@part.net"); } emailer.sendEmail(r, "some message"); } }So the question is, how would you test the
doSomethingCool()
code and ensure that the if/else
conditional evaluation is being done correctly? Granted, this is a simple example, but the question is still valid. There are 2 possibilities.- 1) Implement
equals()
andhashCode()
on theRecipient
object so that their implementation, specificallyequals()
, could be used in your unit test as follows.
public class TestFoo { @Test public void test_doSomethingCool() { { Recipient recOne = new Recipient("foodaddy", "foo@part.net"); Emailer mockEmailer = EasyMock.createMock(Emailer.class); // this next line is the key - we're relying on the fact that // recOne.equals(recipient) == true - where recipient is the // one created in doSomethingCool() EasyMock.expect(mockEmailer.sendEmail(recOne, ...)).andReturn(true); EasyMock.replay(mockEmailer); Foo myFoo = new Foo(mockEmailer); myFoo.doSomethingCool(true); EasyMock.verify(mockEmailer); } { Recipient recTwo = new Recipient("noreply", "noone@part.net"); Emailer mockEmailer = EasyMock.createMock(Emailer.class); // same comment as before EasyMock.expect(mockEmailer.sendEmail(recTwo...)).andReturn(true); EasyMock.replay(mockEmailer); Foo myFoo = new Foo(mockEmailer); myFoo.doSomethingCool(false); EasyMock.verify(mockEmailer); } } }As was noted in the code, the key is that we're relying on the implementation of
Recipient.equals()
.On the test code line:
mockEmailer.sendEmail(recOne, ...);two things are occurring:
- You are setting up an expectation that the
sendEmail()
method will be called - The
Recipient
that is passed tosendEmail()
will be equal to the one provided.
Recipient
object instance (recOne != recipient
) is not used in the expectation, the two different instances are considered equal:recOne.equals(recipient) == trueBy not including an argument matcher explicitly, EasyMock is using object equality (
equals()
) to match. This is the same as using the eq()
argument matcher.While this approach is certainly valid, it's less than ideal because it requires that
equals()
be implemented. Furthermore, the implementation of equals()
has to be known and understood how to be taken advantage of (i.e. you have to know that name
and email
are used in equals()
rather than something like an id
attribute). Additionally, you're not able to actually test/assert against the attribute values of the created Recipient
. That's essentially what's being done by taking advantage of the implementation of equals()
, but it's less explicit and obvious to the poor maintenance developer.- The second (and better) approach is to use an EasyMock
Capture
as follows to retrieve, err capture, the actual instance of theRecipient
object created by thedoSomethingCool()
method.
public class TestFoo { @Test public void test_doSomethingCool() { { Emailer mockEmailer = EasyMock.createMock(Emailer.class); CaptureUsing arCap = new Capture (); EasyMock.expect(mockEmailer.sendEmail( EasyMock.capture(rCap), ...)).andReturn(true); EasyMock.replay(mockEmailer); Foo myFoo = new Foo(mockEmailer); myFoo.doSomethingCool(true); EasyMock.verify(mockEmailer); // actual Recipient instance returned here Recipient recipient = rCap.getValue(); Assert.assertEquals(recipient.getName(), "foodaddy"); Assert.assertEquals(recipient.getEmail(), "foo@part.net"); } { Emailer mockEmailer = EasyMock.createMock(Emailer.class); Capture rCap = new Capture (); EasyMock.expect(mockEmailer.sendEmail( EasyMock.capture(rCap), ...)).andReturn(true); EasyMock.replay(mockEmailer); Foo myFoo = new Foo(mockEmailer); myFoo.doSomethingCool(false); EasyMock.verify(mockEmailer); // actual Recipient instance returned here Recipient recipient = rCap.getValue(); Assert.assertEquals(recipient.getName(), "noreply"); Assert.assertEquals(recipient.getEmail(), "noone@part.net"); } } }
Capture
has none of the requirements/drawback/quirks as the first approach and is certainly no harder to use.
4 comments:
Great thanks Frank! Your post is incredibly helpful.
Thanks for the post Frank, it was quite helpful!
Really nice - I've been using EasyMock for about 2 years, but these tips gave me a lot of new insight. Thanks for posting :)
Thanks a lot. Very helpful.
Post a Comment