diff --git a/spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java b/spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java index 6d37763835e..411b3254ddb 100644 --- a/spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java @@ -17,14 +17,17 @@ package org.springframework.test.context; import java.lang.reflect.Constructor; +import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.ClassUtils; +import org.springframework.util.MultiValueMap; import static org.springframework.beans.BeanUtils.*; -import static org.springframework.core.annotation.AnnotationUtils.*; /** * {@code BootstrapUtils} is a collection of utility methods to assist with @@ -120,9 +123,19 @@ abstract class BootstrapUtils { Class clazz = null; try { - BootstrapWith bootstrapWith = findAnnotation(testClass, BootstrapWith.class); - if (bootstrapWith != null && !TestContextBootstrapper.class.equals(bootstrapWith.value())) { - clazz = bootstrapWith.value(); + + MultiValueMap attributesMultiMap = AnnotatedElementUtils.getAllAnnotationAttributes( + testClass, BootstrapWith.class.getName()); + List values = (attributesMultiMap == null ? null : attributesMultiMap.get(AnnotationUtils.VALUE)); + + if (values != null) { + if (values.size() != 1) { + String msg = String.format( + "Configuration error: found multiple declarations of @BootstrapWith on test class [%s] with values %s", + testClass.getName(), values); + throw new IllegalStateException(msg); + } + clazz = (Class) values.get(0); } else { clazz = (Class) ClassUtils.forName( @@ -130,7 +143,8 @@ abstract class BootstrapUtils { } if (logger.isDebugEnabled()) { - logger.debug(String.format("Instantiating TestContextBootstrapper from class [%s]", clazz.getName())); + logger.debug(String.format("Instantiating TestContextBootstrapper for test class [%s] from class [%s]", + testClass.getName(), clazz.getName())); } TestContextBootstrapper testContextBootstrapper = instantiateClass(clazz, TestContextBootstrapper.class); @@ -139,6 +153,10 @@ abstract class BootstrapUtils { return testContextBootstrapper; } catch (Throwable t) { + if (t instanceof IllegalStateException) { + throw (IllegalStateException) t; + } + throw new IllegalStateException("Could not load TestContextBootstrapper [" + clazz + "]. Specify @BootstrapWith's 'value' attribute " + "or make the default bootstrapper class available.", t); diff --git a/spring-test/src/test/java/org/springframework/test/context/BootstrapUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/BootstrapUtilsTests.java new file mode 100644 index 00000000000..8efb56d3ca9 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/BootstrapUtilsTests.java @@ -0,0 +1,127 @@ +/* + * Copyright 2002-2015 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.test.context; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import org.springframework.test.context.support.DefaultTestContextBootstrapper; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; +import static org.springframework.test.context.BootstrapUtils.*; + +/** + * Unit tests for {@link BootstrapUtils}. + * + * @author Sam Brannen + * @since 4.2 + */ +public class BootstrapUtilsTests { + + private final CacheAwareContextLoaderDelegate delegate = mock(CacheAwareContextLoaderDelegate.class); + + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Test + public void resolveTestContextBootstrapperForNonAnnotatedClass() { + assertBootstrapper(NonAnnotatedClass.class, DefaultTestContextBootstrapper.class); + } + + @Test + public void resolveTestContextBootstrapperWithEmptyBootstrapWithAnnotation() { + BootstrapContext bootstrapContext = BootstrapTestUtils.buildBootstrapContext(EmptyBootstrapWithAnnotationClass.class, delegate); + + exception.expect(IllegalStateException.class); + exception.expectMessage(containsString("Specify @BootstrapWith's 'value' attribute")); + + resolveTestContextBootstrapper(bootstrapContext); + } + + @Test + public void resolveTestContextBootstrapperWithDirectBootstrapWithAnnotation() { + assertBootstrapper(DirectBootstrapWithAnnotationClass.class, FooBootstrapper.class); + } + + @Test + public void resolveTestContextBootstrapperWithInheritedBootstrapWithAnnotation() { + assertBootstrapper(InheritedBootstrapWithAnnotationClass.class, FooBootstrapper.class); + } + + @Test + public void resolveTestContextBootstrapperWithMetaBootstrapWithAnnotation() { + assertBootstrapper(MetaAnnotatedBootstrapWithAnnotationClass.class, BarBootstrapper.class); + } + + @Test + public void resolveTestContextBootstrapperWithDoubleMetaBootstrapWithAnnotation() { + BootstrapContext bootstrapContext = BootstrapTestUtils.buildBootstrapContext( + DoubleMetaAnnotatedBootstrapWithAnnotationClass.class, delegate); + + exception.expect(IllegalStateException.class); + exception.expectMessage(containsString("found multiple declarations of @BootstrapWith")); + exception.expectMessage(containsString(FooBootstrapper.class.getName())); + exception.expectMessage(containsString(BarBootstrapper.class.getName())); + + resolveTestContextBootstrapper(bootstrapContext); + } + + private void assertBootstrapper(Class testClass, Class expectedBootstrapper) { + BootstrapContext bootstrapContext = BootstrapTestUtils.buildBootstrapContext(testClass, delegate); + TestContextBootstrapper bootstrapper = resolveTestContextBootstrapper(bootstrapContext); + assertNotNull(bootstrapper); + assertEquals(expectedBootstrapper, bootstrapper.getClass()); + } + + // ------------------------------------------------------------------- + + static class FooBootstrapper extends DefaultTestContextBootstrapper {} + + static class BarBootstrapper extends DefaultTestContextBootstrapper {} + + @BootstrapWith(FooBootstrapper.class) + @Retention(RetentionPolicy.RUNTIME) + static @interface BootWithFoo {} + + @BootstrapWith(BarBootstrapper.class) + @Retention(RetentionPolicy.RUNTIME) + static @interface BootWithBar {} + + static class NonAnnotatedClass {} + + @BootstrapWith + static class EmptyBootstrapWithAnnotationClass {} + + @BootstrapWith(FooBootstrapper.class) + static class DirectBootstrapWithAnnotationClass {} + + static class InheritedBootstrapWithAnnotationClass extends DirectBootstrapWithAnnotationClass {} + + @BootWithBar + static class MetaAnnotatedBootstrapWithAnnotationClass {} + + @BootWithBar + @BootWithFoo + static class DoubleMetaAnnotatedBootstrapWithAnnotationClass {} + +}