Due to a breaking change in Mockito's internal API it is not possible to execute tests and an IllegalAccessError is thrown (see below)
Formerly Mockito's MockUtils had a public constructor but with this change the class becomes a utility class with no directly accessible constructor. (see https://github.com/mockito/mockito/commit/3fe3b62d2bc5ac60bb85c4bed8870f51109d167c#diff-321079f7242b016035f4577222dfe7a3R26)
This change breaks org.springframework.boot.test.mock.mockito.MockReset (see https://github.com/spring-projects/spring-boot/blob/master/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockReset.java#L56)
Tested with Spring Boot 1.4.0.RELEASE and mockito-core 2.0.94-beta
2016-07-31 15:39:57.810 WARN 12128 --- [ Test worker] o.s.test.context.TestContextManager : Caught exception while allowing TestExecutionListener [org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener@32d199f2] to process 'before' execution of test method [public void de.adesso.adlacarte.springcloud.auth.ExampleControllerTest.testExample() throws java.lang.Exception] for test instance [de.adesso.adlacarte.springcloud.auth.ExampleControllerTest@3b984f0f]
java.lang.IllegalAccessError: tried to access method org.mockito.internal.util.MockUtil.<init>()V from class org.springframework.boot.test.mock.mockito.MockReset
at org.springframework.boot.test.mock.mockito.MockReset.<clinit>(MockReset.java:56) ~[spring-boot-test-1.4.0.RELEASE.jar:1.4.0.RELEASE]
at org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener.beforeTestMethod(ResetMocksTestExecutionListener.java:44) ~[spring-boot-test-1.4.0.RELEASE.jar:1.4.0.RELEASE]
at org.springframework.test.context.TestContextManager.beforeTestMethod(TestContextManager.java:269) ~[spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) [junit-4.12.jar:4.12]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) [junit-4.12.jar:4.12]
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) [junit-4.12.jar:4.12]
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) [junit-4.12.jar:4.12]
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) [junit-4.12.jar:4.12]
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) [junit-4.12.jar:4.12]
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.junit.runners.ParentRunner.run(ParentRunner.java:363) [junit-4.12.jar:4.12]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.runTestClass(JUnitTestClassExecuter.java:114) [gradle-testing-jvm-2.14.1.jar:2.14.1]
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.execute(JUnitTestClassExecuter.java:57) [gradle-testing-jvm-2.14.1.jar:2.14.1]
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassProcessor.processTestClass(JUnitTestClassProcessor.java:66) [gradle-testing-jvm-2.14.1.jar:2.14.1]
at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51) [gradle-testing-base-2.14.1.jar:2.14.1]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_102]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_102]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_102]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_102]
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35) [gradle-messaging-2.14.1.jar:2.14.1]
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) [gradle-messaging-2.14.1.jar:2.14.1]
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32) [gradle-messaging-2.14.1.jar:2.14.1]
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93) [gradle-messaging-2.14.1.jar:2.14.1]
at com.sun.proxy.$Proxy2.processTestClass(Unknown Source) [na:na]
at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:109) [gradle-testing-base-2.14.1.jar:2.14.1]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_102]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_102]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_102]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_102]
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35) [gradle-messaging-2.14.1.jar:2.14.1]
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) [gradle-messaging-2.14.1.jar:2.14.1]
at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:377) [gradle-messaging-2.14.1.jar:2.14.1]
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:54) [gradle-base-services-2.14.1.jar:2.14.1]
at org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:40) [gradle-base-services-2.14.1.jar:2.14.1]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_102]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_102]
at java.lang.Thread.run(Thread.java:745) [na:1.8.0_102]
2016-07-31 15:39:57.947 WARN 12128 --- [ Test worker] o.s.test.context.TestContextManager : Caught exception while allowing TestExecutionListener [org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener@32d199f2] to process 'after' execution for test: method [public void de.adesso.adlacarte.springcloud.auth.ExampleControllerTest.testExample() throws java.lang.Exception], instance [de.adesso.adlacarte.springcloud.auth.ExampleControllerTest@3b984f0f], exception [java.lang.IllegalAccessError: tried to access method org.mockito.internal.util.MockUtil.<init>()V from class org.springframework.boot.test.mock.mockito.MockReset]
java.lang.NoClassDefFoundError: Could not initialize class org.springframework.boot.test.mock.mockito.MockReset
at org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener.afterTestMethod(ResetMocksTestExecutionListener.java:49) ~[spring-boot-test-1.4.0.RELEASE.jar:1.4.0.RELEASE]
at org.springframework.test.context.TestContextManager.afterTestMethod(TestContextManager.java:319) ~[spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:94) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) [junit-4.12.jar:4.12]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) [junit-4.12.jar:4.12]
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) [junit-4.12.jar:4.12]
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) [junit-4.12.jar:4.12]
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) [junit-4.12.jar:4.12]
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) [junit-4.12.jar:4.12]
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.junit.runners.ParentRunner.run(ParentRunner.java:363) [junit-4.12.jar:4.12]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.runTestClass(JUnitTestClassExecuter.java:114) [gradle-testing-jvm-2.14.1.jar:2.14.1]
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.execute(JUnitTestClassExecuter.java:57) [gradle-testing-jvm-2.14.1.jar:2.14.1]
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassProcessor.processTestClass(JUnitTestClassProcessor.java:66) [gradle-testing-jvm-2.14.1.jar:2.14.1]
at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51) [gradle-testing-base-2.14.1.jar:2.14.1]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_102]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_102]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_102]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_102]
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35) [gradle-messaging-2.14.1.jar:2.14.1]
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) [gradle-messaging-2.14.1.jar:2.14.1]
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32) [gradle-messaging-2.14.1.jar:2.14.1]
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93) [gradle-messaging-2.14.1.jar:2.14.1]
at com.sun.proxy.$Proxy2.processTestClass(Unknown Source) [na:na]
at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:109) [gradle-testing-base-2.14.1.jar:2.14.1]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_102]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_102]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_102]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_102]
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35) [gradle-messaging-2.14.1.jar:2.14.1]
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) [gradle-messaging-2.14.1.jar:2.14.1]
at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:377) [gradle-messaging-2.14.1.jar:2.14.1]
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:54) [gradle-base-services-2.14.1.jar:2.14.1]
at org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:40) [gradle-base-services-2.14.1.jar:2.14.1]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_102]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_102]
at java.lang.Thread.run(Thread.java:745) [na:1.8.0_102]
We'll need to switch to reflection calls I guess.
Marking as an enhancement because Mockto 2 isn't officially supported yet.
Hey, Mockito developer here. Just found out this usage of MockUtil. We would like to discuss possible improvements of our public API to prevent usage of our internal API. Recently we have introduced a listener which can be observed by frameworks to receive some notifications, we might be able to expand this to other usecases as well. Please let us know what we can do :)
Hi @TimvdLippe, thanks for the offer of help!
We currently use MockUtil
in a few places. This specific stack trace is related to a trick we use in org.springframework.boot.test.mock.mockito.MockReset
. We basically need to offer different reset strategies when a mock is created that we can actually apply later. To do this we attach a dummy InvocationListener when the mock is created, then later on use MockUtil
to get it back again. If there were a different way to attach arbitrary meta-data to a mock instance, that would be very helpful.
@TimvdLippe The other places we use it are:
isSpy(...)
method).@philwebb The mock meta-data seems like an interesting usecase! Are you willing to create an issue on https://github.com/mockito/mockito with a proposed API to make this as easy as possible for a user. We can use that as a starting point to investigate how we can integrate this into the current framework :tada:
Regarding the SpyDefinition, that should not be broken with the static method, only the constructor of MockUtil. Does not really seem like a hack, so I am fine with it.
Lastly regarding Spring AOP: I am sorry but I have no experience with Spring or any of its proxies. @marcingrzejszczak are you familiar with this code and maybe know what is going on?
@TimvdLippe sure thing. See https://github.com/mockito/mockito/issues/539
@TimvdLippe @philwebb yeah I can check it out tomorrow
I think ideally we'd need a first class way to tell if mocking has started and optionally switch out the target. The problem we have is that Spring AOP provides its own proxy which can get in the way.
So for example, say you're using @Cachable
, this will create a cache aspect to take care of either calling the target method or returning a previously cached result. When using Mockito result is something like this:
+-AOP Proxy----------+
| |
| +-Mock Proxy---+ |
| | | |
| | +--------+ | |
| | | Target | | |
| | +--------+ | |
| | | |
| +--------------+ |
| |
+--------------------+
If two calls are made to a @Cachable
method the second one never reaches the target:
[ AOP ] [ Mock ] [ Target ]
| | |
-->|----------->|----------->|
<--|<-----------|<-----------|
| | |
-->| | |
<--| | |
Usually this is fine, but in the case of a verify(...)
call it's critical that _all_ calls make it. To do that we effectively add another aspect to bypass Spring AOP entirely and change the mock used by MockAwareVerificationMode
to be the real target and not the AOP proxy.
Is there a workaround for this at the moment? Perhaps a way to disable the automated mock reseting? I'm stuck at the moment between this bug and https://github.com/mockito/mockito/issues/72 . I'm trying to do integration testing in Groovy using injected mocks--see http://stackoverflow.com/questions/39485118/mocking-a-groovy-service-with-spring-boot for a more complete description of the problem I'm running into.
@mhworth We don't officially support Mockito 2.0 yet. If you need to use it so that you can use Groovy, then I think your best bet at the moment is to not use @MockBean
.
Is there an ETA on this one? It's a shame we can't use the java8 lambda features present in mockito2.
@agoston No precise ETA. The issue's assigned to 1.5.0.RC1.
+1
+1
How does one disable the MockReset feature? It's crashing my test suite while I never use any MockBean (but I am using the latest version of Mockito). Am I missing something?
Replying to my own question:
The @TestExecutionListeners
annotation on the test class can list the listeners to use.
Out of my debugger, the list built by default is the following:
// org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener
// org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener
// org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener
// org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener
// org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener
// org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener
// org.springframework.test.context.web.ServletTestExecutionListener
// org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener
// org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener
// org.springframework.test.context.support.DirtiesContextTestExecutionListener
// org.springframework.test.context.transaction.TransactionalTestExecutionListener
// org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener
My tests didn't actually need any, so I annotated my test with the following (in Kotlin):
@TestExecutionListeners(listeners = arrayOf())
@dam5s: I found a workaround without having to remove all execution listeners: https://github.com/spring-projects/spring-boot/issues/2572#issuecomment-272144022
I found the workaround after some tries base on comment of @dam5s
I add the code like this:
@TestExecutionListeners(listeners = {SpringBootDependencyInjectionTestExecutionListener.class, ServletTestExecutionListener.class})
spring.boot version:1.4.2.RELEASE
org.mockito version:2.12.0
Most helpful comment
Hey, Mockito developer here. Just found out this usage of MockUtil. We would like to discuss possible improvements of our public API to prevent usage of our internal API. Recently we have introduced a listener which can be observed by frameworks to receive some notifications, we might be able to expand this to other usecases as well. Please let us know what we can do :)