From abb8e3663ae68450750553c8c400d2eaa6df5fe3 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Thu, 5 Jan 2017 16:28:59 -0800 Subject: [PATCH] Polish Mockito 2.0 support --- .../boot/test/mock/mockito/MockReset.java | 2 +- .../MockitoAopProxyTargetInterceptor.java | 25 +- .../boot/test/mock/mockito/MockitoApi.java | 224 ++++++++++++++++++ .../test/mock/mockito/SpringBootMockUtil.java | 142 ----------- .../mock/mockito/MockDefinitionTests.java | 2 +- .../test/mock/mockito/Mockito25Tests.java | 59 +++++ .../test/mock/mockito/SpyDefinitionTests.java | 2 +- 7 files changed, 301 insertions(+), 155 deletions(-) create mode 100644 spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoApi.java delete mode 100644 spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpringBootMockUtil.java create mode 100644 spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/Mockito25Tests.java diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockReset.java b/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockReset.java index 63eab8bce59..b4d69307331 100644 --- a/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockReset.java +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockReset.java @@ -105,7 +105,7 @@ public enum MockReset { MockReset reset = MockReset.NONE; if (ClassUtils.isPresent("org.mockito.internal.util.MockUtil", null)) { if (Mockito.mockingDetails(mock).isMock()) { - MockCreationSettings settings = SpringBootMockUtil.getMockSettings(mock); + MockCreationSettings settings = MockitoApi.get().getMockSettings(mock); List listeners = settings.getInvocationListeners(); for (Object listener : listeners) { if (listener instanceof ResetInvocationListener) { 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 1824c648a4a..b60d08c5d81 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 @@ -24,6 +24,7 @@ import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.mockito.internal.matchers.LocalizedMatcher; import org.mockito.internal.progress.ArgumentMatcherStorage; +import org.mockito.internal.progress.MockingProgress; import org.mockito.internal.verification.MockAwareVerificationMode; import org.mockito.verification.VerificationMode; @@ -52,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 @@ -88,10 +89,15 @@ class MockitoAopProxyTargetInterceptor implements MethodInterceptor { private final Object monitor = new Object(); + private final MockingProgress progress; + + public Verification(Object target) { + this.progress = MockitoApi.get().mockingProgress(target); + } + public boolean isVerifying() { synchronized (this.monitor) { - VerificationMode mode = SpringBootMockUtil.mockingProgress() - .pullVerificationMode(); + VerificationMode mode = this.progress.pullVerificationMode(); if (mode != null) { resetVerificationStarted(mode); return true; @@ -102,13 +108,13 @@ class MockitoAopProxyTargetInterceptor implements MethodInterceptor { public void replaceVerifyMock(Object source, Object target) { synchronized (this.monitor) { - VerificationMode mode = SpringBootMockUtil.mockingProgress() - .pullVerificationMode(); + VerificationMode mode = this.progress.pullVerificationMode(); if (mode != null) { if (mode instanceof MockAwareVerificationMode) { MockAwareVerificationMode mockAwareMode = (MockAwareVerificationMode) mode; if (mockAwareMode.getMock() == source) { - mode = new MockAwareVerificationMode(target, mockAwareMode); + mode = MockitoApi.get().createMockAwareVerificationMode( + target, mockAwareMode); } } resetVerificationStarted(mode); @@ -117,11 +123,10 @@ class MockitoAopProxyTargetInterceptor implements MethodInterceptor { } private void resetVerificationStarted(VerificationMode mode) { - ArgumentMatcherStorage storage = SpringBootMockUtil.mockingProgress() - .getArgumentMatcherStorage(); + ArgumentMatcherStorage storage = this.progress.getArgumentMatcherStorage(); List matchers = storage.pullLocalizedMatchers(); - SpringBootMockUtil.mockingProgress().verificationStarted(mode); - SpringBootMockUtil.reportMatchers(storage, matchers); + 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 new file mode 100644 index 00000000000..b24ba40495f --- /dev/null +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoApi.java @@ -0,0 +1,224 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +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 java.util.Set; + +import org.mockito.Answers; +import org.mockito.ArgumentMatcher; +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; +import org.mockito.stubbing.Answer; +import org.mockito.verification.VerificationMode; + +import org.springframework.beans.BeanUtils; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; + +/** + * A facade for Mockito APIs that have changed between Mockito 1 and Mockito 2. + * + * @author Andy Wilkinson + * @author Stephane Nicoll + * @author Phillip Webb + */ +abstract class MockitoApi { + + private static final MockitoApi api = createApi(); + + /** + * Return mock settings for the given mock object. + * @param mock the mock object + * @return the mock creation settings + */ + public abstract MockCreationSettings getMockSettings(Object mock); + + /** + * Return the mocking progress for the current thread. + * @param mock the bean under test + * @return the current mocking progress + */ + public abstract MockingProgress mockingProgress(Object mock); + + /** + * Set report matchers to the given storage. + * @param storage the storage to use + * @param matchers the matchers to set + */ + public abstract void reportMatchers(ArgumentMatcherStorage storage, + List matchers); + + /** + * Create a new {@link MockAwareVerificationMode} instance. + * @param mock the source mock + * @param mode the verification mode + * @return a new {@link MockAwareVerificationMode} instance + */ + public abstract MockAwareVerificationMode createMockAwareVerificationMode(Object mock, + VerificationMode mode); + + /** + * Return the {@link Answer} for a given {@link Answers} value. + * @param answer the source answers + * @return the answer + */ + public abstract Answer getAnswer(Answers answer); + + /** + * Factory to create the appropriate API version. + * @return the API version + */ + private static MockitoApi createApi() { + if (ClassUtils.isPresent("org.mockito.quality.MockitoHint", null)) { + return new Mockito2Api(); + } + return new Mockito1Api(); + } + + /** + * Get the API for the running mockito version. + * @return the API + */ + public static MockitoApi get() { + return api; + } + + /** + * {@link MockitoApi} for Mockito 1.0. + */ + private static class Mockito1Api extends MockitoApi { + + @Override + public MockCreationSettings getMockSettings(Object mock) { + return new MockUtil().getMockSettings(mock); + } + + @Override + public MockingProgress mockingProgress(Object mock) { + MockUtil mockUtil = new MockUtil(); + InternalMockHandler handler = mockUtil.getMockHandler(mock); + InvocationContainer container = handler.getInvocationContainer(); + Field field = ReflectionUtils.findField(container.getClass(), + "mockingProgress"); + ReflectionUtils.makeAccessible(field); + return (MockingProgress) ReflectionUtils.getField(field, container); + } + + @Override + public void reportMatchers(ArgumentMatcherStorage storage, + List matchers) { + for (LocalizedMatcher matcher : matchers) { + storage.reportMatcher(matcher); + } + } + + @Override + public MockAwareVerificationMode createMockAwareVerificationMode(Object mock, + VerificationMode mode) { + return new MockAwareVerificationMode(mock, mode); + } + + @Override + public Answer getAnswer(Answers answer) { + return answer.get(); + } + + } + + /** + * {@link MockitoApi} for Mockito 2.0. + */ + private static class Mockito2Api extends MockitoApi { + + private final Method getMockSettingsMethod; + + private final Method mockingProgressMethod; + + private final Method reportMatcherMethod; + + private final Method getMatcherMethod; + + private Constructor mockAwareVerificationModeConstructor; + + public Mockito2Api() { + this.getMockSettingsMethod = ReflectionUtils.findMethod(MockUtil.class, + "getMockSettings", Object.class); + this.mockingProgressMethod = ReflectionUtils + .findMethod(ThreadSafeMockingProgress.class, "mockingProgress"); + this.reportMatcherMethod = ReflectionUtils.findMethod( + ArgumentMatcherStorage.class, "reportMatcher", ArgumentMatcher.class); + this.getMatcherMethod = ReflectionUtils.findMethod(LocalizedMatcher.class, + "getMatcher"); + this.mockAwareVerificationModeConstructor = ClassUtils + .getConstructorIfAvailable(MockAwareVerificationMode.class, + Object.class, VerificationMode.class, Set.class); + } + + @Override + public MockCreationSettings getMockSettings(Object mock) { + return (MockCreationSettings) ReflectionUtils + .invokeMethod(this.getMockSettingsMethod, null, mock); + } + + @Override + public MockingProgress mockingProgress(Object mock) { + return (MockingProgress) ReflectionUtils + .invokeMethod(this.mockingProgressMethod, null); + } + + @Override + public void reportMatchers(ArgumentMatcherStorage storage, + List matchers) { + for (LocalizedMatcher matcher : matchers) { + ReflectionUtils.invokeMethod(this.reportMatcherMethod, storage, + ReflectionUtils.invokeMethod(this.getMatcherMethod, matcher)); + } + } + + @Override + public MockAwareVerificationMode createMockAwareVerificationMode(Object mock, + VerificationMode mode) { + if (this.mockAwareVerificationModeConstructor != null) { + // Later 2.0 releases include a listener set + return BeanUtils.instantiateClass( + this.mockAwareVerificationModeConstructor, mock, mode, + Collections.emptySet()); + } + return new MockAwareVerificationMode(mock, mode); + } + + @Override + @SuppressWarnings("unchecked") + public Answer getAnswer(Answers answer) { + return (Answer) ((Object) answer); + } + + } + +} diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpringBootMockUtil.java b/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpringBootMockUtil.java deleted file mode 100644 index b4328856718..00000000000 --- a/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpringBootMockUtil.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright 2012-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.test.mock.mockito; - -import java.lang.reflect.Method; -import java.util.List; - -import org.mockito.ArgumentMatcher; -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.util.MockUtil; -import org.mockito.mock.MockCreationSettings; - -import org.springframework.util.ClassUtils; -import org.springframework.util.ReflectionUtils; - -/** - * A facade for Mockito's {@link MockUtil} that hides API differences between Mockito 1 - * and 2. - * - * @author Andy Wilkinson - */ -final class SpringBootMockUtil { - - private static final MockUtilAdapter adapter; - - static { - if (ClassUtils.isPresent("org.mockito.quality.MockitoHint", - SpringBootMockUtil.class.getClassLoader())) { - adapter = new Mockito2MockUtilAdapter(); - } - else { - adapter = new Mockito1MockUtilAdapter(); - } - } - - private SpringBootMockUtil() { - - } - - static MockCreationSettings getMockSettings(Object mock) { - return adapter.getMockSettings(mock); - } - - static MockingProgress mockingProgress() { - return adapter.mockingProgress(); - } - - static void reportMatchers(ArgumentMatcherStorage storage, - List matchers) { - adapter.reportMatchers(storage, matchers); - } - - private interface MockUtilAdapter { - - MockCreationSettings getMockSettings(Object mock); - - MockingProgress mockingProgress(); - - void reportMatchers(ArgumentMatcherStorage storage, - List matchers); - - } - - private static class Mockito1MockUtilAdapter implements MockUtilAdapter { - - private static final MockingProgress mockingProgress = new ThreadSafeMockingProgress(); - - @Override - public MockCreationSettings getMockSettings(Object mock) { - return new MockUtil().getMockSettings(mock); - } - - @Override - public MockingProgress mockingProgress() { - return mockingProgress; - } - - @Override - public void reportMatchers(ArgumentMatcherStorage storage, - List matchers) { - for (LocalizedMatcher matcher : matchers) { - storage.reportMatcher(matcher); - } - } - - } - - private static class Mockito2MockUtilAdapter implements MockUtilAdapter { - - private final Method getMockSettingsMethod = ReflectionUtils - .findMethod(MockUtil.class, "getMockSettings", Object.class); - - private final Method mockingProgressMethod = ReflectionUtils - .findMethod(ThreadSafeMockingProgress.class, "mockingProgress"); - - private final Method reportMatcherMethod = ReflectionUtils.findMethod( - ArgumentMatcherStorage.class, "reportMatcher", ArgumentMatcher.class); - - private final Method getMatcherMethod = ReflectionUtils - .findMethod(LocalizedMatcher.class, "getMatcher"); - - @Override - public MockCreationSettings getMockSettings(Object mock) { - return (MockCreationSettings) ReflectionUtils - .invokeMethod(this.getMockSettingsMethod, null, mock); - } - - @Override - public MockingProgress mockingProgress() { - return (MockingProgress) ReflectionUtils - .invokeMethod(this.mockingProgressMethod, null); - } - - @Override - public void reportMatchers(ArgumentMatcherStorage storage, - List matchers) { - for (LocalizedMatcher matcher : matchers) { - ReflectionUtils.invokeMethod(this.reportMatcherMethod, storage, - ReflectionUtils.invokeMethod(this.getMatcherMethod, matcher)); - } - } - - } - -} diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockDefinitionTests.java b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockDefinitionTests.java index cb8eaa8feac..b1af1472e7e 100644 --- a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockDefinitionTests.java +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockDefinitionTests.java @@ -85,7 +85,7 @@ public class MockDefinitionTests { new Class[] { ExampleExtraInterface.class }, Answers.RETURNS_SMART_NULLS, true, MockReset.BEFORE, null); ExampleService mock = definition.createMock(); - MockCreationSettings settings = SpringBootMockUtil.getMockSettings(mock); + MockCreationSettings settings = MockitoApi.get().getMockSettings(mock); assertThat(mock).isInstanceOf(ExampleService.class); assertThat(mock).isInstanceOf(ExampleExtraInterface.class); assertThat(settings.getMockName().toString()).isEqualTo("name"); diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/Mockito25Tests.java b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/Mockito25Tests.java new file mode 100644 index 00000000000..7edbe6821a9 --- /dev/null +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/Mockito25Tests.java @@ -0,0 +1,59 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.mock.mockito; + +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; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for compatibility with Mockito 2.5 + * + * @author Andy Wilkinson + * @author Phillip Webb + */ +@RunWith(ModifiedClassPathRunner.class) +@ClassPathOverrides("org.mockito:mockito-core:2.5.4") +public class Mockito25Tests { + + @Test + public void resetMocksTestExecutionListenerTestsWithMockito2() { + runTests(ResetMocksTestExecutionListenerTests.class); + } + + @Test + public void spyBeanWithAopProxyTestsWithMockito2() { + runTests(SpyBeanWithAopProxyTests.class); + } + + 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/SpyDefinitionTests.java b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyDefinitionTests.java index 47b800bbd20..25f769ad051 100644 --- a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyDefinitionTests.java +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpyDefinitionTests.java @@ -78,7 +78,7 @@ public class SpyDefinitionTests { SpyDefinition definition = new SpyDefinition("name", REAL_SERVICE_TYPE, MockReset.BEFORE, true, null); RealExampleService spy = definition.createSpy(new RealExampleService("hello")); - MockCreationSettings settings = SpringBootMockUtil.getMockSettings(spy); + MockCreationSettings settings = MockitoApi.get().getMockSettings(spy); assertThat(spy).isInstanceOf(ExampleService.class); assertThat(settings.getMockName().toString()).isEqualTo("name"); assertThat(settings.getDefaultAnswer())