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 af7700e92f3..1060636db4c 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 2ddb91701ea..0feaf7f230d 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 272834d4a51..0236b94ddfa 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 dbaeb9a1128..5a044d41434 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 4442faa9ff0..9fc1a9eddfe 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 0287b21da8b..9927cf0912e 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 a0afed221a9..d9f03e8234e 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 5cef1c2cc52..63eab8bce59 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 19507b27b86..1824c648a4a 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 00000000000..f47dc11decc --- /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 252dd8ce6bc..172b05aed21 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 4f4dcbda24f..cb8eaa8feac 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 00000000000..495a772080f --- /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 00000000000..57df08053a4 --- /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 8bfc6d9b1ff..c9a1a55ae3b 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 b92af3d2b57..47b800bbd20 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 5eb19b4b477..16b412cab15 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 14ebd3061f0..e70e949f965 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 3781a38ec78..b42c053a64d 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 00000000000..93af3c074eb --- /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 c89aa161e76..c7f05b4866e 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 73ef93cb801..40803d8e4b8 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 00000000000..8d51b230347 --- /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"); + } + +}