窥探EasyMock(1)基础使用篇
窥探EasyMock(2)进阶使用篇
关于EasyMock常见的几个问题, 这里( http://ozgwei.blogspot.com/2007/06/easymock2-quirk.html)有几点, 我做个翻译:
★EasyMock在录制的时候最典型的写法:
expect(mockEmployeeRepository.findByFirstNameAndLastName("John", "Doe")).andReturn(employees);
★如果有个要录制的方法只知道类型不知道具体的值, 可以这样写:
expect(mockEmployeeRepository.findBySpecification(isA(EmployeeSearchSpecification.class)) .andReturn(employees);
★出现这样的异常:
java.lang.IllegalStateException: 2 matchers expected, 1 recorded.
可能是设置mock方法的期望调用方式时, 既使用了isA的方式来指定参数, 又使用了一个具体值来作为参数
比如这样写:
expect(mockEmployeeRepository.findByDepartmentAndSpecification("HR", isA(EmployeeSearchSpecification.class)).andReturn(emplooyees);
正确的写法:
expect(mockEmployeeRepository.findByDepartmentAndSpecification(eq("HR"), isA(EmployeeSearchSpecification.class)).andReturn(employees);
★andReturn()只是用于当mock对象的方法需要有返回值的情况下,手动设置这个方法的返回值给调用的测试类的。在本例中methodABC (arguments,……)方法就需要在recorder的时候用andReturn()方法指定返回值给TargetClass。如果对于有返回值的方法不指定其返回值,在测试的时候会抛出”java.lang.IllegalStateException: missing behavior definition for the preceeding method call XXX”异常。
★一般不能这样写:
EasyMock.expect(itemPropertyManager.processPublishOrEditSpu(EasyMock.isA(SpuDO.class), EasyMock.isA(BaseResultDO.class))).andReturn(EasyMock.isA(BaseResultDO.class));
而应该这样写:
EasyMock.expect(itemPropertyManager.processPublishOrEditSpu(EasyMock.isA(SpuDO.class), EasyMock.isA(BaseResultDO.class))).andReturn(null));
否则会抛出这样的异常:
引用
java.lang.IllegalStateException: matcher calls were not used outside expectations
at org.easymock.internal.RecordState.replay(RecordState.java:72)
at org.easymock.internal.MocksControl.replay(MocksControl.java:57)
at org.easymock.EasyMock.replay(EasyMock.java:1280)
也就是说, 返回值必须给一个具体的值, 而不能只指定返回值类型
★在实用expect来设置mock方法的期望调用方式时, 如果使用到基本类型, 但是又不要基本类型的值, 一般不要这样写:
EasyMock.expect( keywordsChecker.checkNormalKeywords(EasyMock .isA(String.class), EasyMock.isA(Long.class), EasyMock.isA(String.class))).andReturn("");
而应该这样写:
EasyMock.expect(keywordsChecker.checkNormalKeywords(EasyMock.isA(String.class), EasyMock.anyLong(), EasyMock.isA(String.class))).andReturn("");
★EasyMock还有一个很让人郁闷的地方, 比如一个方法的参数可能为null, 而你在测试的时候又恰恰传了一个空值, 则这个测试是没法通过的, 会出现类似下面的异常:
引用
java.lang.AssertionError:
Unexpected method call checkFixKeywords(null, 50010815):
checkFixKeywords(isA(java.lang.String),): expected: 1, actual: 0
checkNormalKeywords(isA(java.lang.String),, isA(java.lang.String)): expected: 1, actual: 0
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:45)
Unexpected method call checkFixKeywords(null, 50010815):
checkFixKeywords(isA(java.lang.String),
checkNormalKeywords(isA(java.lang.String),
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:45)
这也就要求被测试的方法, 参数不能传递空值.
经过跟踪这个应该是EasyMock的一个bug:
public class InstanceOf implements IArgumentMatcher { private final Class> clazz; public InstanceOf(Class clazz) { this.clazz = clazz; } public boolean matches(Object actual) { // 问题在这里: return (actual != null) && clazz.isAssignableFrom(actual.getClass()); // 应该这样写: return (actual == null) || clazz.isAssignableFrom(actual.getClass()); } public void appendTo(StringBuffer buffer) { buffer.append("isA(" + clazz.getName() + ")"); } }
不过这个问题也不是不能绕过, 我用了下面的做法:
EasyMock.expect( keywordsChecker.checkFixKeywords( (String) EasyMock.isNull(), EasyMock.anyLong())) .andReturn(""); EasyMock.expect( keywordsChecker.checkFixKeywords( EasyMock.isA(String.class), EasyMock.anyLong())) .andReturn("").anyTimes();
后来我问了一下EasyMock开发者Tammo Freese, 看来不是能算一个bug, 他做了如下的回答:
引用
this is expected behavior, and it is also documented.
The isA() matcher does the same thing as instanceof , so for null, it returns false.
If you would like to match any Object, use
anyObject()
If you would like to match either Strings or null, use
or(isA(String.class), isNull())
The isA() matcher does the same thing as instanceof , so for null, it returns false.
If you would like to match any Object, use
anyObject()
If you would like to match either Strings or null, use
or(isA(String.class), isNull())
这里录制了两个expect:将null和非空值分开, 但是第二个的返回值注意加上anyTimes(), 因为我的第二种情况会调用多次, 如果两种情况都会调用多次, 则都加上该方法即可, 否则会出现类似下面的异常:
引用
java.lang.AssertionError:
Unexpected method call checkFixKeywords("new test", 50010815):
checkFixKeywords(isA(java.lang.String),): expected: 1, actual: 1 (+1)
checkNormalKeywords(isA(java.lang.String),, isA(java.lang.String)): expected: 1, actual: 0
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:32)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:61)
Unexpected method call checkFixKeywords("new test", 50010815):
checkFixKeywords(isA(java.lang.String),
checkNormalKeywords(isA(java.lang.String),
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:32)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:61)
★静态方法是没法用EasyMock进行mock的
★如果需要expect的方法没有返回值, 应该这样写:
先执行mock的要调用的方法, 然后调用EasyMock.expectLastCall().anyTimes();
★如果是对具体类进行mock, 则需要使用org.easymock.classextension.EasyMock去替换org.easymock.EasyMock, 当然这个需要加入对easymockclassextension的引用, 具体使用上则没有区别, 如果采用maven构建工程, 则可以采用下面的写法:
org.easymock easymock 2.4 org.easymock easymockclassextension 2.2.1
★通常对一个方法进行测试的时候, mock一个对象的情况比较多见, 于是我写了下面的一个比较通用的抽象类:
public abstract class AbstractMockExecutor{ protected T mock; /** * @param clazz * @return */ protected T createMock(Class clazz) { mock = EasyMock.createMock(clazz); return mock; } public void execute() throws Exception { record(); EasyMock.replay(mock); invoke(); verify(); } /** * 创建mock对象, 并对要mock的方法进行方法录制, 必须在该方法中手动调用{@link #createMock(Class)}方法.
* 该方法的一般做法如下: * ** createMock(MockClass); * EasyMock.expect(mock.mockMethod(mockArgument...)).andReturn( * mockResult); * BeanObject.setter(mock); ** * @throws Exception */ protected abstract void record() throws Exception; /** * 调用经过mock之后的执行过程 */ protected abstract void invoke() throws Exception; /** * 验证mock是否正确 */ public void verify() { EasyMock.verify(mock); } }
2012-01 更新:
有时候我们会碰到这样的异常信息:
matcher calls were used outside expectations
后来在网上google了一把, 这里有个说明.
简单的说就是andReturn(xxx)中的xxx不能使用EasyMock.anyObject()这样的方法来指定结果. 但是它没有说解决办法. 这里可以利用andAnswer(), 比如这样写:
final AtomicLong count = new AtomicLong(10000L); EasyMock.expect(db.count()).andAnswer(new IAnswer() { @Override public Long answer() throws Throwable { return count.getAndIncrement(); } }).times(10);