这是一个很神奇的错误。

常规的出错是因为在mock方法里,其中某一个或者几个参数使用了EasyMock.anyxx(),而其他的使用了具体的值。

java.lang.IllegalStateException: 1 matchers expected, 5 recorded.
This exception usually occurs when matchers are mixed with raw values when recording a method:
foo(5, eq(6)); // wrong
You need to use no matcher at all or a matcher for every single param:
foo(eq(5), eq(6)); // right
foo(5, 6); // also right
at org.easymock.internal.ExpectedInvocation.createMissingMatchers(ExpectedInvocation.java:52)
at org.easymock.internal.ExpectedInvocation.<init>(ExpectedInvocation.java:41)
at org.easymock.internal.RecordState.invoke(RecordState.java:51)
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:40)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:101)
at org.easymock.internal.ClassProxyFactory$MockMethodInterceptor.intercept(ClassProxyFactory.java:97)
at com.unit_test_easymock_mockito.database.MyDatabase$$EnhancerByCGLIB$$f0fe0b8a.updateBook(<generated>)
at com.unit_test_easymock_mockito.zexception.BookDaoImplTest.updateBookTest(BookDaoImplTest.java:85)

但本例中出现的错误并不是这个原因,附上代码:

/**
* 更新书本信息 单元测试
*/
@Test
public void updateBookTest() { Book book = new Book(); myDatabase.updateBook(EasyMock.anyObject());
EasyMock.expectLastCall(); mockControl.replay(); bookDaoImpl.updateBook(book); mockControl.verify();
}

可以看到,这里mock的myDatabase.updateBook(EasyMock.anyObject());只有一个参数,不存在一个为anyObject(),一个为具体值的情况。

并且,单独执行junit是正常通过的:

但通过maven clean install打包时,就报错了。

我们来通过debug的方式,看看错误到底出在了什么地方。

可以看到在这里报错了,错误信息也和之前报出的一致。

再仔细分析,发现是这一句代码导致的抛异常:

if (matchers.size() != invocation.getArguments().length) {
invocation.getArguments().length是调用方法的参数个数,那么matchers.size()又是什么呢,从哪里获取的呢?
继续分析代码:
public ExpectedInvocation(Invocation invocation, List<IArgumentMatcher> matchers) {
this.invocation = invocation;
this.matchers = createMissingMatchers(invocation, matchers);
}
createMissingMatchers方法是在ExpectedInvocation构造方法里调用的,再来看:
public Object invoke(Invocation invocation) {
closeMethod();
List<IArgumentMatcher> lastMatchers = LastControl.pullMatchers();
lastInvocation = new ExpectedInvocation(invocation, lastMatchers);
lastInvocationUsed = false;
return emptyReturnValueFor(invocation.getMethod().getReturnType());
}

在invoke方法里,会将matchers传递给ExpectedInvocation,而matchers是通过LastControl.pullMatchers()获取的:

public static List<IArgumentMatcher> pullMatchers() {
List<IArgumentMatcher> stack = threadToArgumentMatcherStack.get();
if (stack == null) {
return null;
}
threadToArgumentMatcherStack.remove();
return new ArrayList<>(stack);
}

这里可以看出,matchers最终是从threadToArgumentMatcherStack里获取的,并且,在获取后,会及时的threadToArgumentMatcherStack.remove();

再来了解下threadToArgumentMatcherStack是什么以及matchers是什么时候放到threadToArgumentMatcherStack里的:

private static final ThreadLocal<List<IArgumentMatcher>> threadToArgumentMatcherStack = new ThreadLocal<>();

threadToArgumentMatcherStack是一个ThreadLocal

public static void reportMatcher(IArgumentMatcher matcher) {
List<IArgumentMatcher> stack = threadToArgumentMatcherStack.get();
if (stack == null) {
stack = new ArrayList<>(5); // methods of more than 5 parameters are quite rare
threadToArgumentMatcherStack.set(stack);
}
stack.add(matcher);
}

在reportMatcher方法里,会把matchers放到threadToArgumentMatcherStack里。

那么,reportMatcher方法又是在什么时候调用的呢?

可以看出,在进行对参数的Mock的时候,anyxx(),都会调用,添加matcher。

那么问题来了,在pullMatchers()方法里,每次获取时都会及时进行清空,为什么出现“1 matchers expected, 5 recorded.”,明明只有一个参数,matchers却被添加了5次的情况?

思考分析一下,发现有一种可能性,就是调用了reportMatcher方法,但是没有调用pullMatchers()方法,这样,对于ThreadLocal类型的threadToArgumentMatcherStack,在同一个线程里,matchers会一直增加。

但对于EasyMock来说,Mock一个方法必然会调用pullMatchers()方法,那么是不是可能没有使用EasyMock来Mock方法,但却使用了EasyMock.anyObject()来Mock参数了呢?

结果果然如此:

@Test
public void queryBookByIdTest() { Integer id = 1; Mockito.when(bookDao.queryBookById(EasyMock.anyObject())).thenReturn(null); bookServiceImpl.queryBookById(id);
}

在这个单元测试里,使用了Mockito来mock方法,却同时使用了EasyMock.anyObject()来mock参数,这样就导致执行到真正EasyMock来mock方法的时候,出问题了,matchers和方法的参数对应不上,导致了问题出现。

最后还存在一个疑问:为什么单独执行单元测试不会报错,而执行clean install打包时就报出这个错呢?

个人推测执行单元测试时,是对各个单元测试方法单个执行的,即单元测试之间不会相互影响;而clean install打包时,所有的单元测试方法在同一个线程里具有相同的上下文,导致了问题出现。

所以说,在项目里写单元测试时,尽量只使用一种单元测试框架,混合使用多种单元测试框架,可能会造成很神奇的问题出现。

最新文章

  1. 基于Composer Player 模型加载和相关属性设置
  2. PDO概念 分析 练习
  3. 在web.xml注册applicationContext.xml配置文件
  4. iOS 服务器回空数据的处理
  5. 使用vue给导航栏添加链接
  6. 谈FME批量自动化数据转换方法
  7. Java 中常用缓存Cache机制的实现《二》
  8. MIT 6.828 JOS学习笔记0. 写在前面的话
  9. Node之集群
  10. Vue自带的过滤器
  11. 如何在Linux上实现文件系统的自动检查和修复?
  12. [html] 前端角度出发做好SEO需要考虑什么
  13. WinForm Control - DataGridView
  14. MyBatis(6):MyBatis集成Spring事务管理(下)
  15. maven配置文件里改动默认jre
  16. Neutron网络性能测试与分析(一) CVR
  17. c++11线程池
  18. iptables命令使用详解
  19. spring IOC源码分析(ApplicationContext)
  20. BZOJ3786星系探索——非旋转treap(平衡树动态维护dfs序)

热门文章

  1. CPU中的主要的寄存器
  2. express 4 使用session和cookies
  3. SpringBoot学习笔记(四):SpringBoot集成Mybatis-Plus+代码生成
  4. 学而有道--思维导图式总结(一):Nosql分类
  5. Python学习day08-python进阶(2)-内置方法
  6. TensorFlow实战Google深度学习框架-人工智能教程-自学人工智能的第二天-深度学习
  7. HDU--2191 汶川地震购米(多重背包)
  8. 11-6-es5选项卡
  9. CodeChef:Chef and Problems(分块)
  10. Codeforces 500D. New Year Santa Network