Fail if multiple @BootstrapWith's are present
Prior to this commit it was possible for two @BootstrapWith annotations to be 'present' on a test class -- for example, via competing custom composed annotations. However, only one of the annotations will ever be used to bootstrap the TestContext Framework. Thus, in such scenarios one of the annotations will be silently ignored. This commit introduces a check for such scenarios. BootstrapUtils' resolveTestContextBootstrapper() method now throws an IllegalStateException if more than one @BootstrapWith annotation is 'present' on a given test class. Issue: SPR-12602
This commit is contained in:
parent
706d3adf07
commit
e85e9768c5
|
@ -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<? extends TestContextBootstrapper> clazz = null;
|
||||
try {
|
||||
BootstrapWith bootstrapWith = findAnnotation(testClass, BootstrapWith.class);
|
||||
if (bootstrapWith != null && !TestContextBootstrapper.class.equals(bootstrapWith.value())) {
|
||||
clazz = bootstrapWith.value();
|
||||
|
||||
MultiValueMap<String, Object> attributesMultiMap = AnnotatedElementUtils.getAllAnnotationAttributes(
|
||||
testClass, BootstrapWith.class.getName());
|
||||
List<Object> 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<? extends TestContextBootstrapper>) values.get(0);
|
||||
}
|
||||
else {
|
||||
clazz = (Class<? extends TestContextBootstrapper>) 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);
|
||||
|
|
|
@ -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 {}
|
||||
|
||||
}
|
Loading…
Reference in New Issue