From 1cd781b2426f1b63b80d990e19d7d7c6a6fd735a Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 18 Nov 2016 15:58:58 +0000 Subject: [PATCH] Make spring-boot-test compatible with Mockito 2.1 and 2.2 We use some internal Mockito classes and some breaking API changes have been made to them in Mockito 2. This commit introduces a utility class, SpringBootMockUtil, to shield our code from these differences. Mockito 1 is called directly and Mockito 2 is called via reflection. To allow these changes to be tested, FilteredClassPathRunner has been enhanced to also support overriding a dependency on the class path. As a result it has been renamed to ModifiedClassPathRunner. The new ClassPathOverrides annotation can be used to provide the Maven coordinates of one or more dependencies that should be resolved and added to the class path. Such additions are added to the start of the class path so that they override any existing dependency that contains the same classes. Closes gh-6520 --- ...rtiesAutoConfigurationNoSecurityTests.java | 4 +- ...rityHealthMvcEndpointIntegrationTests.java | 4 +- ...eanConditionTypeDeductionFailureTests.java | 4 +- ...ourceBeanCreationFailureAnalyzerTests.java | 4 +- ...eddedDataSourceAutoConfigurationTests.java | 4 +- .../SpringApplicationBuilderExampleTests.java | 4 +- spring-boot-test/pom.xml | 6 + .../boot/test/mock/mockito/MockReset.java | 6 +- .../MockitoAopProxyTargetInterceptor.java | 35 ++--- .../test/mock/mockito/SpringBootMockUtil.java | 138 ++++++++++++++++++ .../boot/test/mock/mockito/SpyDefinition.java | 5 +- .../mock/mockito/MockDefinitionTests.java | 3 +- .../test/mock/mockito/Mockito21Tests.java | 54 +++++++ .../test/mock/mockito/Mockito22Tests.java | 54 +++++++ .../mockito/MockitoPostProcessorTests.java | 5 +- .../test/mock/mockito/SpyDefinitionTests.java | 3 +- spring-boot/pom.xml | 58 ++++++++ ...lidationExceptionFailureAnalyzerTests.java | 4 +- .../boot/testutil/ClassPathExclusions.java | 2 +- .../boot/testutil/ClassPathOverrides.java | 42 ++++++ ...nner.java => ModifiedClassPathRunner.java} | 85 +++++++++-- ...difiedClassPathRunnerExclusionsTests.java} | 6 +- ...ModifiedClassPathRunnerOverridesTests.java | 48 ++++++ 23 files changed, 509 insertions(+), 69 deletions(-) create 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/Mockito21Tests.java create mode 100644 spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/Mockito22Tests.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/testutil/ClassPathOverrides.java rename spring-boot/src/test/java/org/springframework/boot/testutil/{FilteredClassPathRunner.java => ModifiedClassPathRunner.java} (64%) rename spring-boot/src/test/java/org/springframework/boot/testutil/{FilteredClassPathRunnerTests.java => ModifiedClassPathRunnerExclusionsTests.java} (88%) create mode 100644 spring-boot/src/test/java/org/springframework/boot/testutil/ModifiedClassPathRunnerOverridesTests.java diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ManagementServerPropertiesAutoConfigurationNoSecurityTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ManagementServerPropertiesAutoConfigurationNoSecurityTests.java index af7700e92f..1060636db4 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ManagementServerPropertiesAutoConfigurationNoSecurityTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ManagementServerPropertiesAutoConfigurationNoSecurityTests.java @@ -22,7 +22,7 @@ import org.junit.runner.RunWith; import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.boot.testutil.ClassPathExclusions; -import org.springframework.boot.testutil.FilteredClassPathRunner; +import org.springframework.boot.testutil.ModifiedClassPathRunner; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import static org.assertj.core.api.Assertions.assertThat; @@ -33,7 +33,7 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Stephane Nicoll */ -@RunWith(FilteredClassPathRunner.class) +@RunWith(ModifiedClassPathRunner.class) @ClassPathExclusions("spring-security-*.jar") public class ManagementServerPropertiesAutoConfigurationNoSecurityTests { diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/NoSpringSecurityHealthMvcEndpointIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/NoSpringSecurityHealthMvcEndpointIntegrationTests.java index 2ddb91701e..0feaf7f230 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/NoSpringSecurityHealthMvcEndpointIntegrationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/NoSpringSecurityHealthMvcEndpointIntegrationTests.java @@ -31,7 +31,7 @@ import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; import org.springframework.boot.testutil.ClassPathExclusions; -import org.springframework.boot.testutil.FilteredClassPathRunner; +import org.springframework.boot.testutil.ModifiedClassPathRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.mock.web.MockServletContext; @@ -49,7 +49,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. * * @author Andy Wilkinson */ -@RunWith(FilteredClassPathRunner.class) +@RunWith(ModifiedClassPathRunner.class) @ClassPathExclusions("spring-security-*.jar") public class NoSpringSecurityHealthMvcEndpointIntegrationTests { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/OnBeanConditionTypeDeductionFailureTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/OnBeanConditionTypeDeductionFailureTests.java index 272834d4a5..0236b94ddf 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/OnBeanConditionTypeDeductionFailureTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/OnBeanConditionTypeDeductionFailureTests.java @@ -22,7 +22,7 @@ import org.junit.runner.RunWith; import org.springframework.boot.autoconfigure.condition.OnBeanCondition.BeanTypeDeductionException; import org.springframework.boot.testutil.ClassPathExclusions; -import org.springframework.boot.testutil.FilteredClassPathRunner; +import org.springframework.boot.testutil.ModifiedClassPathRunner; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -38,7 +38,7 @@ import static org.junit.Assert.fail; * * @author Andy Wilkinson */ -@RunWith(FilteredClassPathRunner.class) +@RunWith(ModifiedClassPathRunner.class) @ClassPathExclusions("jackson-core-*.jar") public class OnBeanConditionTypeDeductionFailureTests { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBeanCreationFailureAnalyzerTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBeanCreationFailureAnalyzerTests.java index dbaeb9a112..5a044d4143 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBeanCreationFailureAnalyzerTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBeanCreationFailureAnalyzerTests.java @@ -23,7 +23,7 @@ import org.springframework.beans.factory.BeanCreationException; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.diagnostics.FailureAnalysis; import org.springframework.boot.testutil.ClassPathExclusions; -import org.springframework.boot.testutil.FilteredClassPathRunner; +import org.springframework.boot.testutil.ModifiedClassPathRunner; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Configuration; @@ -34,7 +34,7 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Andy Wilkinson */ -@RunWith(FilteredClassPathRunner.class) +@RunWith(ModifiedClassPathRunner.class) @ClassPathExclusions({ "h2-*.jar", "hsqldb-*.jar" }) public class DataSourceBeanCreationFailureAnalyzerTests { diff --git a/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/DevToolsEmbeddedDataSourceAutoConfigurationTests.java b/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/DevToolsEmbeddedDataSourceAutoConfigurationTests.java index 4442faa9ff..9fc1a9eddf 100644 --- a/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/DevToolsEmbeddedDataSourceAutoConfigurationTests.java +++ b/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/DevToolsEmbeddedDataSourceAutoConfigurationTests.java @@ -26,7 +26,7 @@ import org.junit.runner.RunWith; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.testutil.ClassPathExclusions; -import org.springframework.boot.testutil.FilteredClassPathRunner; +import org.springframework.boot.testutil.ModifiedClassPathRunner; import org.springframework.context.ConfigurableApplicationContext; import static org.mockito.Mockito.times; @@ -37,7 +37,7 @@ import static org.mockito.Mockito.verify; * * @author Andy Wilkinson */ -@RunWith(FilteredClassPathRunner.class) +@RunWith(ModifiedClassPathRunner.class) @ClassPathExclusions("tomcat-jdbc-*.jar") public class DevToolsEmbeddedDataSourceAutoConfigurationTests extends AbstractDevToolsDataSourceAutoConfigurationTests { diff --git a/spring-boot-docs/src/test/java/org/springframework/boot/builder/SpringApplicationBuilderExampleTests.java b/spring-boot-docs/src/test/java/org/springframework/boot/builder/SpringApplicationBuilderExampleTests.java index 0287b21da8..9927cf0912 100644 --- a/spring-boot-docs/src/test/java/org/springframework/boot/builder/SpringApplicationBuilderExampleTests.java +++ b/spring-boot-docs/src/test/java/org/springframework/boot/builder/SpringApplicationBuilderExampleTests.java @@ -22,7 +22,7 @@ import org.junit.runner.RunWith; import org.springframework.boot.test.rule.OutputCapture; import org.springframework.boot.testutil.ClassPathExclusions; -import org.springframework.boot.testutil.FilteredClassPathRunner; +import org.springframework.boot.testutil.ModifiedClassPathRunner; import static org.assertj.core.api.Assertions.assertThat; @@ -31,7 +31,7 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Andy Wilkinson */ -@RunWith(FilteredClassPathRunner.class) +@RunWith(ModifiedClassPathRunner.class) @ClassPathExclusions("spring-web-*.jar") public class SpringApplicationBuilderExampleTests { diff --git a/spring-boot-test/pom.xml b/spring-boot-test/pom.xml index a0afed221a..d9f03e8234 100644 --- a/spring-boot-test/pom.xml +++ b/spring-boot-test/pom.xml @@ -112,6 +112,12 @@ true + + org.springframework.boot + spring-boot + test-jar + test + org.jetbrains.kotlin kotlin-runtime 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 5cef1c2cc5..63eab8bce5 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 @@ -20,7 +20,6 @@ import java.util.List; import org.mockito.MockSettings; import org.mockito.Mockito; -import org.mockito.internal.util.MockUtil; import org.mockito.listeners.InvocationListener; import org.mockito.listeners.MethodInvocationReport; import org.mockito.mock.MockCreationSettings; @@ -105,9 +104,8 @@ public enum MockReset { static MockReset get(Object mock) { MockReset reset = MockReset.NONE; if (ClassUtils.isPresent("org.mockito.internal.util.MockUtil", null)) { - MockUtil mockUtil = new MockUtil(); - if (mockUtil.isMock(mock)) { - MockCreationSettings settings = mockUtil.getMockSettings(mock); + if (Mockito.mockingDetails(mock).isMock()) { + MockCreationSettings settings = SpringBootMockUtil.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 19507b27b8..1824c648a4 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 @@ -16,19 +16,14 @@ package org.springframework.boot.test.mock.mockito; -import java.lang.reflect.Field; import java.util.List; import org.aopalliance.aop.Advice; import org.aopalliance.intercept.Interceptor; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; -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.stubbing.InvocationContainer; -import org.mockito.internal.util.MockUtil; import org.mockito.internal.verification.MockAwareVerificationMode; import org.mockito.verification.VerificationMode; @@ -38,7 +33,6 @@ import org.springframework.aop.support.AopUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.util.AopTestUtils; import org.springframework.util.Assert; -import org.springframework.util.ReflectionUtils; /** * AOP {@link Interceptor} that attempts to make AOP proxy beans work with Mockito. Works @@ -58,7 +52,7 @@ class MockitoAopProxyTargetInterceptor implements MethodInterceptor { MockitoAopProxyTargetInterceptor(Object source, Object target) throws Exception { this.source = source; this.target = target; - this.verification = new Verification(target); + this.verification = new Verification(); } @Override @@ -94,21 +88,10 @@ class MockitoAopProxyTargetInterceptor implements MethodInterceptor { private final Object monitor = new Object(); - private final MockingProgress progress; - - Verification(Object target) { - MockUtil mockUtil = new MockUtil(); - InternalMockHandler handler = mockUtil.getMockHandler(target); - InvocationContainer container = handler.getInvocationContainer(); - Field field = ReflectionUtils.findField(container.getClass(), - "mockingProgress"); - ReflectionUtils.makeAccessible(field); - this.progress = (MockingProgress) ReflectionUtils.getField(field, container); - } - public boolean isVerifying() { synchronized (this.monitor) { - VerificationMode mode = this.progress.pullVerificationMode(); + VerificationMode mode = SpringBootMockUtil.mockingProgress() + .pullVerificationMode(); if (mode != null) { resetVerificationStarted(mode); return true; @@ -119,7 +102,8 @@ class MockitoAopProxyTargetInterceptor implements MethodInterceptor { public void replaceVerifyMock(Object source, Object target) { synchronized (this.monitor) { - VerificationMode mode = this.progress.pullVerificationMode(); + VerificationMode mode = SpringBootMockUtil.mockingProgress() + .pullVerificationMode(); if (mode != null) { if (mode instanceof MockAwareVerificationMode) { MockAwareVerificationMode mockAwareMode = (MockAwareVerificationMode) mode; @@ -133,12 +117,11 @@ class MockitoAopProxyTargetInterceptor implements MethodInterceptor { } private void resetVerificationStarted(VerificationMode mode) { - ArgumentMatcherStorage storage = this.progress.getArgumentMatcherStorage(); + ArgumentMatcherStorage storage = SpringBootMockUtil.mockingProgress() + .getArgumentMatcherStorage(); List matchers = storage.pullLocalizedMatchers(); - this.progress.verificationStarted(mode); - for (LocalizedMatcher matcher : matchers) { - storage.reportMatcher(matcher); - } + SpringBootMockUtil.mockingProgress().verificationStarted(mode); + SpringBootMockUtil.reportMatchers(storage, matchers); } } 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 new file mode 100644 index 0000000000..f47dc11dec --- /dev/null +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpringBootMockUtil.java @@ -0,0 +1,138 @@ +/* + * 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 + */ +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(); + } + } + + 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/main/java/org/springframework/boot/test/mock/mockito/SpyDefinition.java b/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpyDefinition.java index 252dd8ce6b..172b05aed2 100644 --- a/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpyDefinition.java +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpyDefinition.java @@ -18,7 +18,6 @@ package org.springframework.boot.test.mock.mockito; import org.mockito.MockSettings; import org.mockito.Mockito; -import org.mockito.internal.util.MockUtil; import org.springframework.core.ResolvableType; import org.springframework.core.style.ToStringCreator; @@ -33,8 +32,6 @@ import org.springframework.util.StringUtils; */ class SpyDefinition extends Definition { - private MockUtil mockUtil = new MockUtil(); - private static final int MULTIPLIER = 31; private final ResolvableType typeToSpy; @@ -87,7 +84,7 @@ class SpyDefinition extends Definition { public T createSpy(String name, Object instance) { Assert.notNull(instance, "Instance must not be null"); Assert.isInstanceOf(this.typeToSpy.resolve(), instance); - if (this.mockUtil.isSpy(instance)) { + if (Mockito.mockingDetails(instance).isSpy()) { return (T) instance; } MockSettings settings = MockReset.withSettings(getReset()); 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 4f4dcbda24..cb8eaa8fea 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 @@ -20,7 +20,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.mockito.Answers; -import org.mockito.internal.util.MockUtil; import org.mockito.mock.MockCreationSettings; import org.springframework.boot.test.mock.mockito.example.ExampleExtraInterface; @@ -86,7 +85,7 @@ public class MockDefinitionTests { new Class[] { ExampleExtraInterface.class }, Answers.RETURNS_SMART_NULLS, true, MockReset.BEFORE, null); ExampleService mock = definition.createMock(); - MockCreationSettings settings = new MockUtil().getMockSettings(mock); + MockCreationSettings settings = SpringBootMockUtil.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/Mockito21Tests.java b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/Mockito21Tests.java new file mode 100644 index 0000000000..495a772080 --- /dev/null +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/Mockito21Tests.java @@ -0,0 +1,54 @@ +/* + * 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.springframework.boot.testutil.ClassPathOverrides; +import org.springframework.boot.testutil.ModifiedClassPathRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for compatibility with Mockito 2.1 + * + * @author Andy Wilkinson + */ +@RunWith(ModifiedClassPathRunner.class) +@ClassPathOverrides("org.mockito:mockito-core:2.1.0") +public class Mockito21Tests { + + @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); + 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 new file mode 100644 index 0000000000..57df08053a --- /dev/null +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/Mockito22Tests.java @@ -0,0 +1,54 @@ +/* + * 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.springframework.boot.testutil.ClassPathOverrides; +import org.springframework.boot.testutil.ModifiedClassPathRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for compatibility with Mockito 2.2 + * + * @author Andy Wilkinson + */ +@RunWith(ModifiedClassPathRunner.class) +@ClassPathOverrides("org.mockito:mockito-core:2.2.0") +public class Mockito22Tests { + + @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); + 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/MockitoPostProcessorTests.java b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoPostProcessorTests.java index 8bfc6d9b1f..c9a1a55ae3 100644 --- a/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoPostProcessorTests.java +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoPostProcessorTests.java @@ -19,7 +19,7 @@ package org.springframework.boot.test.mock.mockito; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.mockito.internal.util.MockUtil; +import org.mockito.Mockito; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.annotation.Qualifier; @@ -80,7 +80,8 @@ public class MockitoPostProcessorTests { context.registerBeanDefinition("beanToBeMocked", factoryBeanDefinition); context.register(MockedFactoryBean.class); context.refresh(); - assertThat(new MockUtil().isMock(context.getBean("beanToBeMocked"))).isTrue(); + assertThat(Mockito.mockingDetails(context.getBean("beanToBeMocked")).isMock()) + .isTrue(); } @Configuration 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 b92af3d2b5..47b800bbd2 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 @@ -20,7 +20,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.mockito.Answers; -import org.mockito.internal.util.MockUtil; import org.mockito.mock.MockCreationSettings; import org.springframework.boot.test.mock.mockito.example.ExampleService; @@ -79,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 = new MockUtil().getMockSettings(spy); + MockCreationSettings settings = SpringBootMockUtil.getMockSettings(spy); assertThat(spy).isInstanceOf(ExampleService.class); assertThat(settings.getMockName().toString()).isEqualTo("name"); assertThat(settings.getDefaultAnswer()) diff --git a/spring-boot/pom.xml b/spring-boot/pom.xml index 5eb19b4b47..16b412cab1 100644 --- a/spring-boot/pom.xml +++ b/spring-boot/pom.xml @@ -290,6 +290,64 @@ org.apache.httpcomponents httpasyncclient test + + + org.apache.maven + maven-aether-provider + + + org.eclipse.sisu.plexus + org.eclipse.sisu + + + + + org.apache.maven + maven-settings-builder + + + org.codehaus.plexus + plexus-component-api + + + * + * + + + + + org.eclipse.aether + aether-api + + + org.eclipse.aether + aether-connector-basic + + + org.eclipse.aether + aether-impl + + + org.eclipse.aether + aether-spi + + + org.eclipse.aether + aether-transport-file + + + org.eclipse.aether + aether-transport-http + + + jcl-over-slf4j + org.slf4j + + + + + org.eclipse.aether + aether-util org.firebirdsql.jdbc diff --git a/spring-boot/src/test/java/org/springframework/boot/diagnostics/analyzer/ValidationExceptionFailureAnalyzerTests.java b/spring-boot/src/test/java/org/springframework/boot/diagnostics/analyzer/ValidationExceptionFailureAnalyzerTests.java index 14ebd3061f..e70e949f96 100644 --- a/spring-boot/src/test/java/org/springframework/boot/diagnostics/analyzer/ValidationExceptionFailureAnalyzerTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/diagnostics/analyzer/ValidationExceptionFailureAnalyzerTests.java @@ -23,7 +23,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.diagnostics.FailureAnalysis; import org.springframework.boot.testutil.ClassPathExclusions; -import org.springframework.boot.testutil.FilteredClassPathRunner; +import org.springframework.boot.testutil.ModifiedClassPathRunner; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import static org.assertj.core.api.Assertions.assertThat; @@ -34,7 +34,7 @@ import static org.junit.Assert.fail; * * @author Andy Wilkinson */ -@RunWith(FilteredClassPathRunner.class) +@RunWith(ModifiedClassPathRunner.class) @ClassPathExclusions("hibernate-validator-*.jar") public class ValidationExceptionFailureAnalyzerTests { diff --git a/spring-boot/src/test/java/org/springframework/boot/testutil/ClassPathExclusions.java b/spring-boot/src/test/java/org/springframework/boot/testutil/ClassPathExclusions.java index 3781a38ec7..b42c053a64 100644 --- a/spring-boot/src/test/java/org/springframework/boot/testutil/ClassPathExclusions.java +++ b/spring-boot/src/test/java/org/springframework/boot/testutil/ClassPathExclusions.java @@ -23,7 +23,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * Annotation used in combination with {@link FilteredClassPathRunner} to exclude entries + * Annotation used in combination with {@link ModifiedClassPathRunner} to exclude entries * from the classpath. * * @author Andy Wilkinson diff --git a/spring-boot/src/test/java/org/springframework/boot/testutil/ClassPathOverrides.java b/spring-boot/src/test/java/org/springframework/boot/testutil/ClassPathOverrides.java new file mode 100644 index 0000000000..93af3c074e --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/testutil/ClassPathOverrides.java @@ -0,0 +1,42 @@ +/* + * 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.testutil; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation used in combination with {@link ModifiedClassPathRunner} to override entries + * on the classpath. + * + * @author Andy Wilkinson + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface ClassPathOverrides { + + /** + * One or more sets of Maven coordinates ({@code groupId:artifactId:version}) to be + * added to the classpath. The additions will take precedence over any existing + * classes on the classpath. + * @return the coordinates + */ + String[] value(); + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/testutil/FilteredClassPathRunner.java b/spring-boot/src/test/java/org/springframework/boot/testutil/ModifiedClassPathRunner.java similarity index 64% rename from spring-boot/src/test/java/org/springframework/boot/testutil/FilteredClassPathRunner.java rename to spring-boot/src/test/java/org/springframework/boot/testutil/ModifiedClassPathRunner.java index c89aa161e7..c7f05b4866 100644 --- a/spring-boot/src/test/java/org/springframework/boot/testutil/FilteredClassPathRunner.java +++ b/spring-boot/src/test/java/org/springframework/boot/testutil/ModifiedClassPathRunner.java @@ -28,6 +28,22 @@ import java.util.List; import java.util.jar.Attributes; import java.util.jar.JarFile; +import org.apache.maven.repository.internal.MavenRepositorySystemUtils; +import org.eclipse.aether.DefaultRepositorySystemSession; +import org.eclipse.aether.RepositorySystem; +import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.collection.CollectRequest; +import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory; +import org.eclipse.aether.graph.Dependency; +import org.eclipse.aether.impl.DefaultServiceLocator; +import org.eclipse.aether.repository.LocalRepository; +import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.resolution.ArtifactResult; +import org.eclipse.aether.resolution.DependencyRequest; +import org.eclipse.aether.resolution.DependencyResult; +import org.eclipse.aether.spi.connector.RepositoryConnectorFactory; +import org.eclipse.aether.spi.connector.transport.TransporterFactory; +import org.eclipse.aether.transport.http.HttpTransporterFactory; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; @@ -38,16 +54,17 @@ import org.springframework.util.AntPathMatcher; import org.springframework.util.StringUtils; /** - * A custom {@link BlockJUnit4ClassRunner} that runs tests using a filtered class path. - * Entries are excluded from the class path using {@link ClassPathExclusions} on the test - * class. A class loader is created with the customized class path and is used both to - * load the test class and as the thread context class loader while the test is being run. + * A custom {@link BlockJUnit4ClassRunner} that runs tests using a modified class path. + * Entries are excluded from the class path using {@link ClassPathExclusions} and + * overridden using {@link ClassPathOverrides} on the test class. A class loader is + * created with the customized class path and is used both to load the test class and as + * the thread context class loader while the test is being run. * * @author Andy Wilkinson */ -public class FilteredClassPathRunner extends BlockJUnit4ClassRunner { +public class ModifiedClassPathRunner extends BlockJUnit4ClassRunner { - public FilteredClassPathRunner(Class testClass) throws InitializationError { + public ModifiedClassPathRunner(Class testClass) throws InitializationError { super(testClass); } @@ -64,7 +81,7 @@ public class FilteredClassPathRunner extends BlockJUnit4ClassRunner { private URLClassLoader createTestClassLoader(Class testClass) throws Exception { URLClassLoader classLoader = (URLClassLoader) this.getClass().getClassLoader(); - return new FilteredClassLoader(filterUrls(extractUrls(classLoader), testClass), + return new FilteredClassLoader(processUrls(extractUrls(classLoader), testClass), classLoader.getParent(), classLoader); } @@ -104,15 +121,61 @@ public class FilteredClassPathRunner extends BlockJUnit4ClassRunner { } } - private URL[] filterUrls(URL[] urls, Class testClass) throws Exception { + private URL[] processUrls(URL[] urls, Class testClass) throws Exception { ClassPathEntryFilter filter = new ClassPathEntryFilter(testClass); - List filteredUrls = new ArrayList(); + List processedUrls = new ArrayList(); + processedUrls.addAll(getAdditionalUrls(testClass)); for (URL url : urls) { if (!filter.isExcluded(url)) { - filteredUrls.add(url); + processedUrls.add(url); } } - return filteredUrls.toArray(new URL[filteredUrls.size()]); + return processedUrls.toArray(new URL[processedUrls.size()]); + } + + private List getAdditionalUrls(Class testClass) throws Exception { + ClassPathOverrides overrides = AnnotationUtils.findAnnotation(testClass, + ClassPathOverrides.class); + if (overrides == null) { + return Collections.emptyList(); + } + return resolveCoordinates(overrides.value()); + } + + private List resolveCoordinates(String[] coordinates) throws Exception { + DefaultServiceLocator serviceLocator = MavenRepositorySystemUtils + .newServiceLocator(); + serviceLocator.addService(RepositoryConnectorFactory.class, + BasicRepositoryConnectorFactory.class); + serviceLocator.addService(TransporterFactory.class, HttpTransporterFactory.class); + RepositorySystem repositorySystem = serviceLocator + .getService(RepositorySystem.class); + DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession(); + LocalRepository localRepository = new LocalRepository( + System.getProperty("user.home") + "/.m2/repository"); + session.setLocalRepositoryManager( + repositorySystem.newLocalRepositoryManager(session, localRepository)); + CollectRequest collectRequest = new CollectRequest(null, + Arrays.asList(new RemoteRepository.Builder("central", "default", + "http://central.maven.org/maven2").build())); + + collectRequest.setDependencies(createDependencies(coordinates)); + DependencyRequest dependencyRequest = new DependencyRequest(collectRequest, null); + DependencyResult result = repositorySystem.resolveDependencies(session, + dependencyRequest); + List resolvedArtifacts = new ArrayList(); + for (ArtifactResult artifact : result.getArtifactResults()) { + resolvedArtifacts.add(artifact.getArtifact().getFile().toURI().toURL()); + } + return resolvedArtifacts; + } + + private List createDependencies(String[] allCoordinates) { + List dependencies = new ArrayList(); + for (String coordinates : allCoordinates) { + dependencies.add(new Dependency(new DefaultArtifact(coordinates), null)); + } + return dependencies; } /** diff --git a/spring-boot/src/test/java/org/springframework/boot/testutil/FilteredClassPathRunnerTests.java b/spring-boot/src/test/java/org/springframework/boot/testutil/ModifiedClassPathRunnerExclusionsTests.java similarity index 88% rename from spring-boot/src/test/java/org/springframework/boot/testutil/FilteredClassPathRunnerTests.java rename to spring-boot/src/test/java/org/springframework/boot/testutil/ModifiedClassPathRunnerExclusionsTests.java index 73ef93cb80..40803d8e4b 100644 --- a/spring-boot/src/test/java/org/springframework/boot/testutil/FilteredClassPathRunnerTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/testutil/ModifiedClassPathRunnerExclusionsTests.java @@ -22,13 +22,13 @@ import org.junit.runner.RunWith; import static org.assertj.core.api.Assertions.assertThat; /** - * Tests for {@link FilteredClassPathRunner} + * Tests for {@link ModifiedClassPathRunner} excluding entries from the class path. * * @author Andy Wilkinson */ -@RunWith(FilteredClassPathRunner.class) +@RunWith(ModifiedClassPathRunner.class) @ClassPathExclusions("hibernate-validator-*.jar") -public class FilteredClassPathRunnerTests { +public class ModifiedClassPathRunnerExclusionsTests { private static final String EXCLUDED_RESOURCE = "META-INF/services/" + "javax.validation.spi.ValidationProvider"; diff --git a/spring-boot/src/test/java/org/springframework/boot/testutil/ModifiedClassPathRunnerOverridesTests.java b/spring-boot/src/test/java/org/springframework/boot/testutil/ModifiedClassPathRunnerOverridesTests.java new file mode 100644 index 0000000000..8d51b23034 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/testutil/ModifiedClassPathRunnerOverridesTests.java @@ -0,0 +1,48 @@ +/* + * 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.testutil; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.context.ApplicationContext; +import org.springframework.util.StringUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ModifiedClassPathRunner} overriding entries on the class path. + * + * @author Andy Wilkinson + */ +@RunWith(ModifiedClassPathRunner.class) +@ClassPathOverrides("org.springframework:spring-context:4.1.0.RELEASE") +public class ModifiedClassPathRunnerOverridesTests { + + @Test + public void classesAreLoadedFromOverride() { + assertThat(ApplicationContext.class.getProtectionDomain().getCodeSource() + .getLocation().toString()).endsWith("spring-context-4.1.0.RELEASE.jar"); + } + + @Test + public void classesAreLoadedFromTransitiveDependencyOfOverride() { + assertThat(StringUtils.class.getProtectionDomain().getCodeSource().getLocation() + .toString()).endsWith("spring-core-4.1.0.RELEASE.jar"); + } + +}