java.lang.IllegalStateException: 1 matchers expected, 5 recorded.

這是一個很神奇的錯誤。java

常規的出錯是由於在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)

但本例中出現的錯誤並非這個緣由,附上代碼:maven

/** * 更新書本信息 單元測試 */ @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打包時,就報錯了。ui

咱們來經過debug的方式,看看錯誤到底出在了什麼地方。this

 

 

 

 

 

 

 

 

 能夠看到在這裏報錯了,錯誤信息也和以前報出的一致。spa

再仔細分析,發現是這一句代碼致使的拋異常:線程

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()獲取的:debug

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打包時,全部的單元測試方法在同一個線程裏具備相同的上下文,致使了問題出現。

因此說,在項目裏寫單元測試時,儘可能只使用一種單元測試框架,混合使用多種單元測試框架,可能會形成很神奇的問題出現。

相關文章
相關標籤/搜索