问题
项目里大量单元测试采用了EasyMock框架,Maven命令跑测试总是报如下错误:
java.lang.IllegalStateException: N matchers expected, M recorded.
打开报错的单元测试,无论是通过命令: mvn test -Dtest=Mytest,还是通过eclipse单独运行,均无法重现问题。
record-replay-verify模型
EasyMock基于: record-replay-verify 模型
EasyMock分三个阶段:
Record
创建mock对象,并定义mock对象的行为。
Replay
在replay阶段,执行被测试的方法。项目组的新人总是忘写replay方法。
Verify
验证mock对象的返回结果
问题分析
问题就出在verify方法上。
查阅资料发现,若测试中抛出一个异常,当前单元测试中的verify方法没有进入,那么EasyMock就继续Record下一个单元测试,并最终在其他单元测试上报错。
查阅到的英文原文如下:
because we throw an exception from a mock object and catch it via JUnit‘s “expected” annotation.
Code never comes to verify() and EasyMock never closes recording state of mock.
EasyMock continues to record states of other tests and this test will cause the other tests to fail.
问题代码示例
@Test (expected = RuntimeException.class)
public void testExperiment() {
Filter filter = createMock(Filter.class);
// 1. Record阶段,记录mock对象上的操作
EasyMock.expect(filter.getById("1001")).andReturn(expectedUser);
// 模拟抛出一个异常
expectLastCall().andThrow(new RuntimeException("test"));
// 2. Replay阶段,这一步才是真正的执行被测试的方法。
replay(filter);
// 3. verify阶段,由于前面产生了异常verify根本就没有进入
verify(filter);
}
解决方法
其中一个方法是所有的单元使用EasyMockSupport创建,然后在@After方法里面调用 resetAll(),重置mock对象,避免污染下一个测试方法。
@After
public void reSetMock() {
easyMockSupport.resetAll();
}
但由于EasyMock使用复杂,定位问题较难。
有些问题能找到原因,有些问题找不到原因,有解决方法即可:全面改用新一代测试框架Mockito。Mockito隐式使用了record-replay-verify,避免了忘写replay造成的问题,避免了被其他测试搞坏。