zoukankan      html  css  js  c++  java
  • java.lang.IllegalStateException: 1 matchers expected, 5 recorded.

    这是一个很神奇的错误。

    常规的出错是因为在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打包时,所有的单元测试方法在同一个线程里具有相同的上下文,导致了问题出现。

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

  • 相关阅读:
    python——socket,IO多路复用(select),socket server实现多并发
    python——多线程,多进程,协程
    python——装饰器,迭代器,生成器
    time模块,datetime模块
    re模块,paramiko模块
    Freemaker中使用中括号来包含标签
    Freemaker中使用中括号来包含标签
    freemarker Velocity获取request,session
    freemarker Velocity获取request,session
    freemarker Velocity获取request,session
  • 原文地址:https://www.cnblogs.com/ld-mars/p/12060991.html
Copyright © 2011-2022 走看看