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:
Sam Brannen 2015-04-26 02:58:07 +02:00
parent 706d3adf07
commit e85e9768c5
2 changed files with 150 additions and 5 deletions

View File

@ -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);

View File

@ -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 {}
}