EasyMockCopyright (c) 2001 OFFIS. EasyMock is a class library that provides an easy way to use mock objects for given interfaces. EasyMock is available under the terms of the MIT license. A mock control object gives a mock object for an interface. In the setup state these two objects (the control and the mock) are used to define valid method calls and return values or thrown exceptions as well as expectations how often a method is called. After changing into the active state, the mock object behaves exactly as specified in the setup state.
Main benefits
Main drawbacks
InstallationEasyMock has been tested on Windows NT and on Windows 98 using Sun's Java 1.3.1 and 1.4 beta.
easymocktests.jar to your class path and execute
'java de.offis.easymock.tests.AllTests' .
UsageAs an example, we use the interfaceRepository :
public interface Repository { void removeText(String name); String getText(String name); boolean exists(String name); void sync() throws IOException; } An empty mock objectThe following examples assume that you are familiar with the JUnit testing framework and with the concept of mock objects. We will now build a test case and toy around with it to understand the functionality of the EasyMock package. To use EasyMocks, we only need one class, an instance of theMockControl interface, that is returned
by the factory EasyMock . As we want to write a test case, we also import
junit.framework.TestCase :
import de.offis.easymock.EasyMock; import de.offis.easymock.MockControl; import junit.framework.TestCase; public class EasyMockTest extends TestCase { public EasyMockTest(String name) { super(name); } protected void setUp() { } public void testEasyMock() { } }To get a usable mock object, we need to
private MockControl control; private Repository mockRepository; protected void setUp() { control = EasyMock.controlFor(Repository.class); // 1 mockRepository = (Repository) control.getMock(); // 2 } public void testEasyMock() { // 3 control.activate(); // 4 // Use the mock object! }After activating in step 4), mockRepository
is a mock object for the Repository interface that implements no behaviour. This means that if we try to call any
of the interface's methods the mock will throw an AssertionFailedError :
public void testEasyMock() { // 3 control.activate(); // 4 mockRepository.removeText("Text"); // throws an AssertionFailedError }throws junit.framework.AssertionFailedError: EasyMock for interface Repository: Unexpected method call removeText("Text") at de.offis.easymock.VerifyingState.invoke(VerifyingState.java:40) at de.offis.easymock.AbstractMockController$1.invoke(AbstractMockController.java:20) at $Proxy8.removeText(Unknown Source) at EasyMockTest.testEasyMock(EasyMockTest.java:24) Adding BehaviourNow we give our mock object some behaviour. We want it to respond tomockRepository.removeText("Text") .
To get this, we have to the following
public void testEasyMock() { mockRepository.removeText("Text"); // 3 control.setVoidCallable(); // 3 control.activate(); // 4 mockRepository.removeText("Text"); // throws an AssertionFailedError }Since Release 0.7, there is also a shortcut: If a void method is called in the setup state, and no behaviour is specified, setVoidCallable()
is assumed automatically. So the line control.setVoidCallable(); // 3
can be removed.
In the setup state, the mock does not behave like a mock, but it is used to make the method calls for which we define the behaviour on the control! If you want to make this more explicit, you can give it an other name in this phase: public void testEasyMock() { MockControl mockControl = EasyMock.controlFor(Repository.class); Repository methodCallControl = (Repository) mockControl.getMock(); methodCallControl.removeText("Text"); mockControl.setVoidCallable(); mockControl.activate(); Repository mockRepository = (Repository) mockControl.getMock(); mockRepository.removeText("Text"); }However, we will stick to using only the mockRepository variable.
If we call junit.framework.AssertionFailedError: EasyMock for interface Repository: Unexpected method call removeText("Text2") at de.offis.easymock.VerifyingState.invoke(VerifyingState.java:40) at de.offis.easymock.AbstractMockController$1.invoke(AbstractMockController.java:20) at $Proxy9.removeText(Unknown Source) at EasyMockTest.testEasyMock(EasyMockTest.java:26) Verifying BehaviourThere is one error case that we have not concerned so far: If we specify a behaviour, we have to verify that it is actually needed! The following code does not show an error:public void testEasyMock() { mockRepository.removeText("Text"); control.setVoidCallable(); control.activate(); // 4 // no usage of specified behaviour! }To verify that all the specified behaviour has been used, we have to call control.verify() :
public void testEasyMock() { mockRepository.removeText("Text"); control.setVoidCallable(); control.activate(); // 4 // no usage of specified behaviour! control.verify(); // throws AssertionFailedError }Then we get the following exception: junit.framework.AssertionFailedError: Expectation failure on verify: EasyMock for interface Repository method call removeText("Text"): expected at least one call at de.offis.easymock.AbstractMockController.verify(AbstractMockController.java:49) at EasyMockTest.testEasyMock(EasyMockTest.java:27) Expecting an explicit number of callsUp to now, our mock only checks whether the method is called at all. We can also specify an explicit call count as parameter tosetVoidCallable() . For testing,
we call the method too many times and see what happens.
public void testEasyMock() { mockRepository.removeText("Text"); control.setVoidCallable(3); control.activate(); // 4 mockRepository.removeText("Text"); mockRepository.removeText("Text"); mockRepository.removeText("Text"); mockRepository.removeText("Text"); // throws AssertionFailedError mockRepository.removeText("Text"); control.verify(); }We get the following exception that tells us that the method has been called too many times. The failure occurs directly at the first method call exceeding the limit. junit.framework.AssertionFailedError: EasyMock for interface Repository: method call removeText("Text"): calls expected: 3, received: 4 at de.offis.easymock.VerifyingState.invoke(VerifyingState.java:40) at de.offis.easymock.AbstractMockController$1.invoke(AbstractMockController.java:20) at $Proxy12.removeText(Unknown Source) at EasyMockTest.testEasyMock(EasyMockTest.java:29)If we delete the last three mockRepository.removeText("Text") -calls, the
control.verify() throws an AssertionFailedError :
junit.framework.AssertionFailedError: Expectation failure on verify: EasyMock for interface Repository method call removeText("Text"): calls expected: 3, received: 2 at de.offis.easymock.AbstractMockController.verify(AbstractMockController.java:49) at EasyMockTest.testEasyMock(EasyMockTest.java:29) Specifying return valuesOur interface also contains the methodsString getText(String name) and
boolean exists(String name) . For specifying return values, the control
has the methods setReturnValue([type] value) and
setReturnValue([type] value, int times) . [type] is either Object
or a primitive type.
As an example, we specify that our mock object should respond to
public void testEasyMock() { mockRepository.getText("Text"); control.setReturnValue("The text you wanted"); mockRepository.getText("Text2"); control.setReturnValue(null, 3); mockRepository.exists("Text"); control.setReturnValue(true, 2); control.activate(); assertEquals("The text you wanted", mockRepository.getText("Text")); assertNull(mockRepository.getText("Text2")); assertTrue(mockRepository.exists("Text")); control.verify(); }In this case, we can see that verify() collects all the verification errors: junit.framework.AssertionFailedError: Expectation failure on verify: EasyMock for interface Repository method call exists("Text"): calls expected: 2, received: 1 method call getText("Text2"): calls expected: 3, received: 1 at de.offis.easymock.AbstractMockController.verify(AbstractMockController.java:49) at EasyMockTest.testEasyMock(EasyMockTest.java:34) Working with ExceptionsFor specifying exceptions (more exact: Throwables) to be thrown, the control has the methodssetThrowable(Throwable throwable) and
setThrowable(Throwable throwable, int times) .
Unchecked exceptions (that is, As an example, we specify that our mock object should respond to
import java.io.IOException; // ... public void testEasyMock() { mockRepository.getText("Text"); control.setThrowable(new RuntimeException(), 2); try { mockRepository.sync(); control.setThrowable(new IOException()); } catch (IOException shouldNeverHappen) { throw new Error("bug in EasyMock library"); } control.activate(); // use the mock ... } Changing behaviour for the same method callIt is also possible to specify a changing behaviour for a method. You wantgetText("Text") to
public void testEasyMock() { mockRepository.getText("Text"); control.setReturnValue("The text you wanted", 2); control.setThrowable(new RuntimeException(), 3); control.setReturnValue("The text you really wanted"); control.activate(); // use the mock ... } InvalidMockUsageExceptionUp to this point, at least one questions is open: What happens if we do something wrong? For example, specify a wrong return value? Or calling verify() in setup mode? Or specifying an Exception that cannot be thrown by the method?
The answer is: In all this cases, a Reusing a mock objectAn easy mock can be reset bycontrol.reset() .
Using default behaviour for methodsUp to now, the default behaviour for each method is to throw anAssertionFailedError .
This can be changed by calling setDefaultReturnValue() ,
setDefaultThrowable() or setDefaultVoidCallable() in the setup state.
The following code configures the mock to answer "The text you wanted" on getText("Text")
and "The default text" for all other parameters to getText() :
public void testEasyMock() { mockRepository.getText("Text"); control.setReturnValue("The text you wanted"); control.setDefaultReturnValue("The default text"); control.activate(); // use the mock ... } Nice mocksOn a mock generated by aMockControl returned by EasyMock.controlFor() ,
the default behaviour for each method is to throw an AssertionFailedError .
If we want a "nice mock" that by default allows all method calls and returns appropriate empty values (0, null or false),
we simply use EasyMock.niceControlFor() .
EasyMock is developed and maintained by Tammo Freese.
Thanks to the beta testers and first users for their helpful comments, suggestions
and bug reports:
Please check the preliminary EasyMock home page for new versions. EasyMock version 0.73 (2001-07-12) Changes since 0.7:
Changes since 0.6:
Changes since 0.5:
|