在重新初始化之后,Mock 对象的状态将被置为 Record 状态。
3.在 EasyMock 中使用参数匹配器
EasyMock 预定义的参数匹配器
在使用 Mock 对象进行实际的测试过程中,EasyMock 会根据方法名和参数来匹配一个预期方法的调用。EasyMock 对参数的匹配默认使用 equals() 方法进行比较。这可能会引起一些问题。例如在上一章节中创建的mockStatement对象:
mockStatement.executeQuery("SELECT * FROM sales_order_table");
expectLastCall().andStubReturn(mockResultSet);
在实际的调用中,我们可能会遇到 SQL 语句中某些关键字大小写的问题,例如将 SELECT 写成 Select,这时在实际的测试中,EasyMock 所采用的默认匹配器将认为这两个参数不匹配,从而造成 Mock 对象的预期方法不被调用。EasyMock 提供了灵活的参数匹配方式来解决这个问题。如果您对 mockStatement 具体执行的语句并不关注,并希望所有输入的字符串都能匹配这一方法调用,您可以用 org.easymock.EasyMock 类所提供的 anyObject 方法来代替参数中的 SQL 语句:
mockStatement.executeQuery( anyObject() );
expectLastCall().andStubReturn(mockResultSet);
anyObject 方法表示任意输入值都与预期值相匹配。除了 anyObject 以外,EasyMock还提供了多个预先定义的参数匹配器,其中比较常用的一些有:
aryEq(X value):通过Arrays.equals()进行匹配,适用于数组对象;
isNull():当输入值为Null时匹配;
notNull():当输入值不为Null时匹配;
same(X value):当输入值和预期值是同一个对象时匹配;
lt(X value), leq(X value), geq(X value), gt(X value):当输入值小于、小等于、大等于、大于预期值时匹配,适用于数值类型;
startsWith(String prefix), contains(String substring), endsWith(String suffix):当输入值以预期值开头、包含预期值、以预期值结尾时匹配,适用于String类型;
matches(String regex):当输入值与正则表达式匹配时匹配,适用于String类型。
自定义参数匹配器
预定义的参数匹配器可能无法满足一些复杂的情况,这时你需要定义自己的参数匹配器。在上一节中,我们希望能有一个匹配器对 SQL 中关键字的大小写不敏感,使用 anyObject 其实并不是一个好的选择。对此,我们可以定义自己的参数匹配器 SQLEquals。
要定义新的参数匹配器,需要实现 org.easymock.IArgumentMatcher 接口。其中,matches(Object actual) 方法应当实现输入值和预期值的匹配逻辑,而在 appendTo(StringBuffer buffer) 方法中,你可以添加当匹配失败时需要显示的信息。以下是 SQLEquals 实现的部分代码(完整的代码可以在 src.zip 中找到):
清单5:自定义参数匹配器SQLEquals
public class SQLEquals implements IArgumentMatcher {
private String expectedSQL = null;
public SQLEquals(String expectedSQL) {
this.expectedSQL = expectedSQL;
}
......
public boolean matches(Object actualSQL) {
if (actualSQL == null && expectedSQL == null)
return true;
else if (actualSQL instanceof String)
return expectedSQL.equalsIgnoreCase((String) actualSQL);
else
return false;
}
}
在实现了 IArgumentMatcher 接口之后,我们需要写一个静态方法将它包装一下。这个静态方法的实现需要将 SQLEquals 的一个对象通过 reportMatcher 方法报告给EasyMock:
清单6:自定义参数匹配器 SQLEquals 静态方法
public static String sqlEquals(String in) {
reportMatcher(new SQLEquals(in));
return in;
}
这样,我们自定义的 sqlEquals 匹配器可以使用了。我们可以将上例中的 executeQuery 方法设定修改如下:
mockStatement.executeQuery(sqlEquals("SELECT * FROM sales_order_table"));
expectLastCall().andStubReturn(mockResultSet);
在使用 executeQuery("select * from sales_order_table") 进行方法调用时,该预期行为将被匹配。
4.特殊的 Mock 对象类型
到目前为止,我们所创建的 Mock 对象都属于 EasyMock 默认的 Mock 对象类型,它对预期方法的调用次序不敏感,对非预期的方法调用抛出 AssertionError。除了这种默认的 Mock 类型以外,EasyMock 还提供了一些特殊的 Mock 类型用于支持不同的需求。
Strick Mock 对象
如果 Mock 对象是通过 EasyMock.createMock() 或是 IMocksControl.createMock() 所创建的,那么在进行 verify 验证时,方法的调用顺序是不进行检查的。如果要创建方法调用的先后次序敏感的 Mock 对象(Strick Mock),应该使用 EasyMock.createStrickMock() 来创建,例如:
ResultSet strickMockResultSet = createStrickMock(ResultSet.class);
类似于 createMock,我们同样可以用 IMocksControl 实例来创建一个 Strick Mock 对象:
IMocksControl control = EasyMock.createStrictControl();
ResultSet strickMockResultSet = control.createMock(ResultSet.class);
Nice Mock 对象
使用 createMock() 创建的 Mock 对象对非预期的方法调用默认的行为是抛出 AssertionError,如果需要一个默认返回0,null 或 false 等"无效值"的 "Nice Mock" 对象,可以通过 EasyMock 类提供的 createNiceMock() 方法创建。类似的,你也可以用
5.EasyMock 的工作原理
EasyMock 是如何为一个特定的接口动态创建 Mock 对象,并记录 Mock 对象预期行为的呢?其实,EasyMock 后台处理的主要原理是利用 java.lang.reflect.Proxy 为指定的接口创建一个动态代理,这个动态代理,是我们在编码中用到的 Mock 对象。EasyMock 还为这个动态代理提供了一个 InvocationHandler 接口的实现,这个实现类的主要功能是将动态代理的预期行为记录在某个映射表中和在实际调用时从这个映射表中取出预期输出。下图是 EasyMock 中主要的功能类:
图4:EasyMock主要功能类
和开发人员联系紧密的是 EasyMock 类,这个类提供了 createMock、replay、verify 等方法以及所有预定义的参数匹配器。