From c4779a10ac2394386b156e54c4f030a8dd619a09 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Thu, 5 Jan 2017 16:27:41 -0800 Subject: [PATCH] Fix Mockito 2 support See gh-7770 --- .../MockitoAopProxyTargetInterceptor.java | 10 +- .../boot/test/mock/mockito/MockitoApi.java | 127 +++++++++--------- .../test/mock/mockito/Mockito110Tests.java | 4 + .../test/mock/mockito/Mockito21Tests.java | 4 + .../test/mock/mockito/Mockito22Tests.java | 4 + .../mockito/SpyBeanWithAopProxyTests.java | 34 ++++- 6 files changed, 116 insertions(+), 67 deletions(-) diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoAopProxyTargetInterceptor.java b/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoAopProxyTargetInterceptor.java index 593b2d06a6..905c58afb8 100644 --- a/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoAopProxyTargetInterceptor.java +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoAopProxyTargetInterceptor.java @@ -53,7 +53,7 @@ class MockitoAopProxyTargetInterceptor implements MethodInterceptor { MockitoAopProxyTargetInterceptor(Object source, Object target) throws Exception { this.source = source; this.target = target; - this.verification = new Verification(); + this.verification = new Verification(target); } @Override @@ -89,7 +89,11 @@ class MockitoAopProxyTargetInterceptor implements MethodInterceptor { private final Object monitor = new Object(); - private final MockingProgress progress = MockitoApi.get().mockingProgress(); + private final MockingProgress progress; + + Verification(Object target) { + this.progress = MockitoApi.get().mockingProgress(target); + } public boolean isVerifying() { synchronized (this.monitor) { @@ -121,7 +125,7 @@ class MockitoAopProxyTargetInterceptor implements MethodInterceptor { private void resetVerificationStarted(VerificationMode mode) { ArgumentMatcherStorage storage = this.progress.getArgumentMatcherStorage(); List matchers = storage.pullLocalizedMatchers(); - MockitoApi.get().mockingProgress().verificationStarted(mode); + this.progress.verificationStarted(mode); MockitoApi.get().reportMatchers(storage, matchers); } diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoApi.java b/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoApi.java index f10428e90d..3af809b17a 100644 --- a/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoApi.java +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoApi.java @@ -17,16 +17,19 @@ package org.springframework.boot.test.mock.mockito; import java.lang.reflect.Constructor; +import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Collections; import java.util.List; import org.hamcrest.Matcher; import org.mockito.Answers; +import org.mockito.internal.InternalMockHandler; import org.mockito.internal.matchers.LocalizedMatcher; import org.mockito.internal.progress.ArgumentMatcherStorage; import org.mockito.internal.progress.MockingProgress; import org.mockito.internal.progress.ThreadSafeMockingProgress; +import org.mockito.internal.stubbing.InvocationContainer; import org.mockito.internal.util.MockUtil; import org.mockito.internal.verification.MockAwareVerificationMode; import org.mockito.mock.MockCreationSettings; @@ -57,9 +60,10 @@ abstract class MockitoApi { /** * Return the mocking progress for the current thread. + * @param mock the mock object * @return the current mocking progress */ - public abstract MockingProgress mockingProgress(); + public abstract MockingProgress mockingProgress(Object mock); /** * Set report matchers to the given storage. @@ -90,11 +94,10 @@ abstract class MockitoApi { * @return the API version */ private static MockitoApi createApi() { - if (!ClassUtils.isPresent("org.mockito.ReturnValues", - MockitoApi.class.getClassLoader())) { - return new Mockito2Api(); + if (ClassUtils.isPresent("org.mockito.ReturnValues", null)) { + return new Mockito1Api(); } - return new Mockito1Api(); + return new Mockito2Api(); } /** @@ -105,54 +108,6 @@ abstract class MockitoApi { return api; } - /** - * {@link MockitoApi} for Mockito 2.0. - */ - private static class Mockito2Api extends MockitoApi { - - @Override - public MockCreationSettings getMockSettings(Object mock) { - return MockUtil.getMockSettings(mock); - } - - @Override - public MockingProgress mockingProgress() { - return ThreadSafeMockingProgress.mockingProgress(); - } - - @Override - public void reportMatchers(ArgumentMatcherStorage storage, - List matchers) { - for (LocalizedMatcher matcher : matchers) { - storage.reportMatcher(matcher.getMatcher()); - } - } - - @Override - public MockAwareVerificationMode createMockAwareVerificationMode(Object mock, - VerificationMode mode) { - try { - return new MockAwareVerificationMode(mock, mode, Collections.emptySet()); - } - catch (NoSuchMethodError ex) { - // Earlier versions of 2.x did not have the collection parameter - Constructor constructor = ClassUtils - .getConstructorIfAvailable(MockAwareVerificationMode.class, - Object.class, VerificationMode.class); - if (constructor == null) { - throw ex; - } - return BeanUtils.instantiateClass(constructor, mock, mode); - } - } - - @Override - public Answer getAnswer(Answers answer) { - return answer; - } - - } - /** * {@link MockitoApi} for Mockito 1.0. */ @@ -162,7 +117,7 @@ abstract class MockitoApi { private final Method getMockSettingsMethod; - private final MockingProgress mockingProgress; + private final Method getMockHandlerMethod; private Method reportMatcherMethod; @@ -172,10 +127,8 @@ abstract class MockitoApi { this.mockUtil = BeanUtils.instantiateClass(MockUtil.class); this.getMockSettingsMethod = ReflectionUtils.findMethod(MockUtil.class, "getMockSettings", Object.class); - this.mockingProgress = (MockingProgress) BeanUtils - .instantiateClass(ClassUtils.resolveClassName( - "org.mockito.internal.progress.ThreadSafeMockingProgress", - MockitoApi.class.getClassLoader())); + this.getMockHandlerMethod = ReflectionUtils.findMethod(MockUtil.class, + "getMockHandler", Object.class); this.reportMatcherMethod = ReflectionUtils.findMethod( ArgumentMatcherStorage.class, "reportMatcher", Matcher.class); this.mockAwareVerificationModeConstructor = ClassUtils @@ -190,8 +143,14 @@ abstract class MockitoApi { } @Override - public MockingProgress mockingProgress() { - return this.mockingProgress; + public MockingProgress mockingProgress(Object mock) { + InternalMockHandler handler = (InternalMockHandler) ReflectionUtils + .invokeMethod(this.getMockHandlerMethod, this.mockUtil, mock); + InvocationContainer container = handler.getInvocationContainer(); + Field field = ReflectionUtils.findField(container.getClass(), + "mockingProgress"); + ReflectionUtils.makeAccessible(field); + return (MockingProgress) ReflectionUtils.getField(field, container); } @Override @@ -217,4 +176,52 @@ abstract class MockitoApi { } + /** + * {@link MockitoApi} for Mockito 2.0. + */ + private static class Mockito2Api extends MockitoApi { + + @Override + public MockCreationSettings getMockSettings(Object mock) { + return MockUtil.getMockSettings(mock); + } + + @Override + public MockingProgress mockingProgress(Object mock) { + return ThreadSafeMockingProgress.mockingProgress(); + } + + @Override + public void reportMatchers(ArgumentMatcherStorage storage, + List matchers) { + for (LocalizedMatcher matcher : matchers) { + storage.reportMatcher(matcher.getMatcher()); + } + } + + @Override + public MockAwareVerificationMode createMockAwareVerificationMode(Object mock, + VerificationMode mode) { + try { + return new MockAwareVerificationMode(mock, mode, Collections.emptySet()); + } + catch (NoSuchMethodError ex) { + // Earlier versions of 2.x did not have the collection parameter + Constructor constructor = ClassUtils + .getConstructorIfAvailable(MockAwareVerificationMode.class, + Object.class, VerificationMode.class); + if (constructor == null) { + throw ex; + } + return BeanUtils.instantiateClass(constructor, mock, mode); + } + } + + @Override + public Answer getAnswer(Answers answer) { + return answer; + } + + } + } diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/Mockito110Tests.java b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/Mockito110Tests.java index 5889ab92eb..07a6a5ce54 100644 --- a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/Mockito110Tests.java +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/Mockito110Tests.java @@ -20,6 +20,7 @@ import org.junit.Test; import org.junit.runner.JUnitCore; import org.junit.runner.Result; import org.junit.runner.RunWith; +import org.junit.runner.notification.Failure; import org.springframework.boot.junit.runner.classpath.ClassPathOverrides; import org.springframework.boot.junit.runner.classpath.ModifiedClassPathRunner; @@ -43,6 +44,9 @@ public class Mockito110Tests { private void runTests(Class testClass) { Result result = new JUnitCore().run(testClass); + for (Failure failure : result.getFailures()) { + System.err.println(failure.getTrace()); + } assertThat(result.getFailureCount()).isEqualTo(0); assertThat(result.getRunCount()).isGreaterThan(0); } diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/Mockito21Tests.java b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/Mockito21Tests.java index c6878dea47..63866ca35e 100644 --- a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/Mockito21Tests.java +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/Mockito21Tests.java @@ -20,6 +20,7 @@ import org.junit.Test; import org.junit.runner.JUnitCore; import org.junit.runner.Result; import org.junit.runner.RunWith; +import org.junit.runner.notification.Failure; import org.springframework.boot.junit.runner.classpath.ClassPathOverrides; import org.springframework.boot.junit.runner.classpath.ModifiedClassPathRunner; @@ -47,6 +48,9 @@ public class Mockito21Tests { private void runTests(Class testClass) { Result result = new JUnitCore().run(testClass); + for (Failure failure : result.getFailures()) { + System.err.println(failure.getTrace()); + } assertThat(result.getFailureCount()).isEqualTo(0); assertThat(result.getRunCount()).isGreaterThan(0); } diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/Mockito22Tests.java b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/Mockito22Tests.java index 967f1b79ba..61fcfd7db2 100644 --- a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/Mockito22Tests.java +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/Mockito22Tests.java @@ -20,6 +20,7 @@ import org.junit.Test; import org.junit.runner.JUnitCore; import org.junit.runner.Result; import org.junit.runner.RunWith; +import org.junit.runner.notification.Failure; import org.springframework.boot.junit.runner.classpath.ClassPathOverrides; import org.springframework.boot.junit.runner.classpath.ModifiedClassPathRunner; @@ -47,6 +48,9 @@ public class Mockito22Tests { private void runTests(Class testClass) { Result result = new JUnitCore().run(testClass); + for (Failure failure : result.getFailures()) { + System.err.println(failure.getTrace()); + } assertThat(result.getFailureCount()).isEqualTo(0); assertThat(result.getRunCount()).isGreaterThan(0); } diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanWithAopProxyTests.java b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanWithAopProxyTests.java index 7f0e33755f..3bf583824e 100644 --- a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanWithAopProxyTests.java +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyBeanWithAopProxyTests.java @@ -16,10 +16,12 @@ package org.springframework.boot.test.mock.mockito; +import java.lang.reflect.Method; import java.util.Arrays; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentMatchers; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.Cacheable; @@ -32,10 +34,10 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.stereotype.Service; import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -58,8 +60,32 @@ public class SpyBeanWithAopProxyTests { Long d2 = this.dateService.getDate(false); assertThat(d1).isEqualTo(d2); verify(this.dateService, times(1)).getDate(false); - verify(this.dateService, times(1)).getDate(eq(false)); - verify(this.dateService, times(1)).getDate(anyBoolean()); + verify(this.dateService, times(1)).getDate(matchesFalse()); + verify(this.dateService, times(1)).getDate(matchesAnyBoolean()); + } + + private boolean matchesFalse() { + if (isTestingMockito1()) { + Method method = ReflectionUtils.findMethod( + ClassUtils.resolveClassName("org.mockito.Matchers", null), "eq", + Boolean.TYPE); + return (boolean) ReflectionUtils.invokeMethod(method, null, false); + } + return ArgumentMatchers.eq(false); + } + + private boolean matchesAnyBoolean() { + if (isTestingMockito1()) { + Method method = ReflectionUtils.findMethod( + ClassUtils.resolveClassName("org.mockito.Matchers", null), + "anyBoolean"); + return (boolean) ReflectionUtils.invokeMethod(method, null); + } + return ArgumentMatchers.anyBoolean(); + } + + private boolean isTestingMockito1() { + return ClassUtils.isPresent("org.mockito.ReturnValues", null); } @Configuration