EasyMock2.2较之之前的版本(EasyMock1.x)在使用上有较大的差别,我们在此通过代码给出EasyMock2.2的使用方法。
以对如下ClassUnderTest类的测试为例:
// ...
public void addListener(Collaborator listener) ...{
// ...
}
public void addDocument(String title, byte[] document) ...{
// ...
}
public boolean removeDocument(String title) ...{
// ...
}
public boolean removeDocuments(String[] titles) ...{
// ...
}
}
其中Collaborator是一个接口,如下:
void documentAdded(String title);
void documentChanged(String title);
void documentRemoved(String title);
byte voteForRemoval(String title);
byte[] voteForRemovals(String[] title);
}
以下使用JUnit3.8,首先给出第一个测试:
import static org.easymock.EasyMock.*;
public class ExampleTest extends TestCase ...{
private ClassUnderTest classUnderTest;
private Collaborator mock;
protected void setUp() ...{
classUnderTest = new ClassUnderTest();
mock = createMock(Collaborator.class);
classUnderTest.addListener(mock);
}
public void testRemoveNonExistingDocument() ...{
// This call should not lead to any notification
// of the Mock Object:
mock.documentAdded("New Document"); // 2
classUnderTest.removeDocument("New Document");
}
}
由以上代码可以看出,创建一个Mock对象的过程如下:
- 针对我们希望模拟的接口创建一个Mock对象
- 在这个Mock对象上记录期望的行为
- 将该Mock对象切换到replay状态
如何设置方法希望被调用次数:
mock.documentAdded("Document");
mock.documentChanged("Document");
expectLastCall().times(3);
replay(mock);
classUnderTest.addDocument("Document", new byte[0]);
classUnderTest.addDocument("Document", new byte[0]);
classUnderTest.addDocument("Document", new byte[0]);
classUnderTest.addDocument("Document", new byte[0]);
verify(mock);
}
如何设置方法希望的返回值:
mock.documentAdded("Document"); // expect document addition
// expect to be asked to vote for document removal, and vote for it
expect(mock.voteForRemoval("Document")).andReturn((byte) 42);
mock.documentRemoved("Document"); // expect document removal
replay(mock);
classUnderTest.addDocument("Document", new byte[0]);
assertTrue(classUnderTest.removeDocument("Document"));
verify(mock);
}
public void testVoteAgainstRemoval() ...{
mock.documentAdded("Document"); // expect document addition
// expect to be asked to vote for document removal, and vote against it
expect(mock.voteForRemoval("Document")).andReturn((byte) -42);
replay(mock);
classUnderTest.addDocument("Document", new byte[0]);
assertFalse(classUnderTest.removeDocument("Document"));
verify(mock);
}
以上代码除了使用:
也可以使用:
expectLastCall().andReturn((byte) 42);
如何设置方法被调用时希望抛出的异常:
使用andThrow(Throwable throwable). 方法代替上面的andReturn即可
对同一方法的链式设置:
.andReturn((byte) 42).times(3)
.andThrow(new RuntimeException(), 4)
.andReturn((byte) -42);
如何设置方法的期望参数:
对如下代码:
expect(mock.voteForRemovals(documents)).andReturn(42);
如果为ClassUnderTest类的voteForRemovals方法调用传递的是内容相同的另一个数组,将会报错,正确的代码如下:
expect(mock.voteForRemovals(aryEq(documents))).andReturn(42);
这里aryEq指定参数匹配规则是内容匹配。
对于方法的期望参数,可以设置“模式匹配”,EasyMock自带的匹配模式函数如下:
eq(X value)
如果实际传过来的参数与value相同,则匹配anyBoolean()
,anyByte()
,anyChar()
,anyDouble()
,anyFloat()
,anyInt()
,anyLong()
,anyObject()
,anyShort()
匹配相应的基本类型的任意值eq(X value, X delta)
在误差delta范围内匹配value,适用于double、float这样的数值类型aryEq(X value)
数组内容匹配.isNull()
如果参数为null,则匹配.notNull()
如果尝试不为null,则匹配.same(X value)
如果期望参数与实际参数是同一个对象,则匹配(即==).isA(Class clazz)
匹配当前类或其子类的实例.lt(X value)
,leq(X value)
,geq(X value)
,gt(X value)
对基本数值类型,以如上不等式匹配.startsWith(String prefix), contains(String substring), endsWith(String suffix)
适用于字符串,分别匹配前缀,包含,后缀.matches(String regex), find(String regex)
以正则表达式regex匹配.and(X first, X second)
first,sencond是这里的任意函数,当他们同时匹配时.or(X first, X second)
first,sencond是这里的任意函数,当他们有一个匹配时匹配.not(X value)
value是这里的任意函数,当其不匹配时匹配.
设置方法调用次数范围的期望:
times(int min, int max)
:to expect betweenmin
andmax
calls,atLeastOnce()
:to expect at least one call, andanyTimes()
:to expected an unrestricted number of calls.
Strict Mocks:
一般的Mock不验证调用次序,只要方法按照设置的期望被调用就可以通过验证,Strict Mocks验证调用次序。
EasyMock.createStrictMock()
对次序验证的开关:
checkOrder(mock, false).
Strict Mock对象与普通Mock对象的区别在于:
- Strict Mock对象的调用次序验证在其创建后就是使能的;
- Strict Mock对象的调用次序验证在对其调用reset后就置为使能;
为方法设置默认行为(Stub行为):
对于某些方法调用,我们并不知道它们是否会实际被调用,但如果被调用,又希望能够控制他们的行为,如下设置即可:
expect(mock.voteForRemoval(not(eq("Document")))).andStubReturn(-1);
nice Mock:
一般的Mock,如果调用了他们没有被预设置的方法,就会抛出异常,使用nice mock,如果遇到这种情况,则返回像null,0,false这样的
如何在Mock之间设定调用顺序
除了使用如下代码:
IMyInterface mock = createStrictMock(IMyInterface.class);
replay(mock);
verify(mock);
reset(mock);
我们可以使用如下等价的代码:
ctrl.replay();
ctrl.verify();
ctrl.reset();
IMocksControl 允许创建多于一个的Mock对象,这样就有可能验证Mock对象之间的调用顺序,如下:
IMyInterface mock1 = ctrl.createMock(IMyInterface.class);
IMyInterface mock2 = ctrl.createMock(IMyInterface.class);
mock1.a();
mock2.a();
ctrl.checkOrder(false);
expectLastCall().anyTimes();
mock2.c();
expectLastCall().anyTimes();
ctrl.checkOrder(true);
mock2.b();
mock1.b();
ctrl.replay();
其实我们使用EasyMock的静态方法了创建和操纵一个Mock对象的做法仅仅是隐藏了控制对象,实际上整个过程还是通过委托给控制对象完成的。
这样的需求如何满足:
在老版的EasyMock中存在如下函数:
MockControl.setDefaultReturnValue();
MockControl.setDefaultThrowable();
MockControl.setDefaultVoidCallable();
它们的应用情况是:对于那些Mock没有为其指定期望,或调用时使用的参数不是期望中所设定的参数的方法来说,其默认行为是抛出AssertionFailedError。可以在执行完mock的设定期望调用之后,使用以上方法中的一种来修改那些调用参数不是指定的期望参数的方法的默认行为。
那么在EasyMock2.2中如上的需求如何满足呢?
EasyMock2.2,对于ReturnValue和Throwable的默认行为,可以使用上面小节中提到的stub方法;而对于调用参数不是指定的期望参数的方法,其实EasyMock2.2中不存在这个问题,因为EasyMock2.2对参数并不是简单匹配,而是设定匹配规则,如上面小节提到的。
比如,对于一个接收MyClass类实例变量为参数,没有返回值的方法setMyClass,如果只是期望它被调用,但并不关心传给它的具体参数,就可以如下设置:
mock.setMyClass((MyClass) anyObject());