diff --git a/spring-test/src/main/java/org/springframework/test/context/NestedTestConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/NestedTestConfiguration.java
index a1e3db386a..fba36b6501 100644
--- a/spring-test/src/main/java/org/springframework/test/context/NestedTestConfiguration.java
+++ b/spring-test/src/main/java/org/springframework/test/context/NestedTestConfiguration.java
@@ -36,8 +36,11 @@ import org.springframework.lang.Nullable;
*
If {@code @NestedTestConfiguration} is not present or
* meta-present on a test class, in its super type hierarchy, or in its
* enclosing class hierarchy, the default enclosing configuration inheritance
- * mode will be used. See {@link #ENCLOSING_CONFIGURATION_PROPERTY_NAME} for
- * details on how to change the default mode.
+ * mode will be used. A {@code @NestedTestConfiguration} declaration on an
+ * enclosing class for a nested interface will be ignored when searching for the
+ * annotation on classes that implement the interface. See
+ * {@link #ENCLOSING_CONFIGURATION_PROPERTY_NAME} for details on how to change
+ * the default mode.
*
*
When the {@link EnclosingConfiguration#INHERIT INHERIT} mode is in use,
* configuration from an enclosing test class will be inherited by inner test
diff --git a/spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java b/spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java
index 27f498c8a8..20abe3e772 100644
--- a/spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java
+++ b/spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java
@@ -19,6 +19,7 @@ package org.springframework.test.util;
import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.Set;
+import java.util.function.Predicate;
import org.springframework.core.SpringProperties;
import org.springframework.core.annotation.AnnotatedElementUtils;
@@ -93,7 +94,15 @@ public abstract class MetaAnnotationUtils {
*/
@Nullable
public static T findMergedAnnotation(Class> clazz, Class annotationType) {
- AnnotationDescriptor descriptor = findAnnotationDescriptor(clazz, annotationType);
+ return findMergedAnnotation(clazz, annotationType, MetaAnnotationUtils::searchEnclosingClass);
+ }
+
+ @Nullable
+ private static T findMergedAnnotation(Class> clazz, Class annotationType,
+ Predicate> searchEnclosingClass) {
+
+ AnnotationDescriptor descriptor =
+ findAnnotationDescriptor(clazz, annotationType, searchEnclosingClass, new HashSet<>());
return (descriptor != null ? descriptor.synthesizeAnnotation() : null);
}
@@ -125,7 +134,8 @@ public abstract class MetaAnnotationUtils {
public static AnnotationDescriptor findAnnotationDescriptor(
Class> clazz, Class annotationType) {
- return findAnnotationDescriptor(clazz, annotationType, new HashSet<>());
+ return findAnnotationDescriptor(clazz, annotationType, MetaAnnotationUtils::searchEnclosingClass,
+ new HashSet<>());
}
/**
@@ -140,7 +150,8 @@ public abstract class MetaAnnotationUtils {
*/
@Nullable
private static AnnotationDescriptor findAnnotationDescriptor(
- @Nullable Class> clazz, Class annotationType, Set visited) {
+ @Nullable Class> clazz, Class annotationType, Predicate> searchEnclosingClass,
+ Set visited) {
Assert.notNull(annotationType, "Annotation type must not be null");
if (clazz == null || Object.class == clazz) {
@@ -152,11 +163,13 @@ public abstract class MetaAnnotationUtils {
return new AnnotationDescriptor<>(clazz, clazz.getAnnotation(annotationType));
}
+ AnnotationDescriptor descriptor = null;
+
// Declared on a composed annotation (i.e., as a meta-annotation)?
for (Annotation composedAnn : clazz.getDeclaredAnnotations()) {
Class extends Annotation> composedType = composedAnn.annotationType();
if (!AnnotationUtils.isInJavaLangAnnotationPackage(composedType.getName()) && visited.add(composedAnn)) {
- AnnotationDescriptor descriptor = findAnnotationDescriptor(composedType, annotationType, visited);
+ descriptor = findAnnotationDescriptor(composedType, annotationType, searchEnclosingClass, visited);
if (descriptor != null) {
return new AnnotationDescriptor<>(
clazz, descriptor.getDeclaringClass(), composedAnn, descriptor.getAnnotation());
@@ -166,7 +179,7 @@ public abstract class MetaAnnotationUtils {
// Declared on an interface?
for (Class> ifc : clazz.getInterfaces()) {
- AnnotationDescriptor descriptor = findAnnotationDescriptor(ifc, annotationType, visited);
+ descriptor = findAnnotationDescriptor(ifc, annotationType, searchEnclosingClass, visited);
if (descriptor != null) {
return new AnnotationDescriptor<>(clazz, descriptor.getDeclaringClass(),
descriptor.getComposedAnnotation(), descriptor.getAnnotation());
@@ -174,15 +187,14 @@ public abstract class MetaAnnotationUtils {
}
// Declared on a superclass?
- AnnotationDescriptor descriptor =
- findAnnotationDescriptor(clazz.getSuperclass(), annotationType, visited);
+ descriptor = findAnnotationDescriptor(clazz.getSuperclass(), annotationType, searchEnclosingClass, visited);
if (descriptor != null) {
return descriptor;
}
// Declared on an enclosing class of an inner class?
- if (searchEnclosingClass(clazz)) {
- descriptor = findAnnotationDescriptor(clazz.getEnclosingClass(), annotationType, visited);
+ if (searchEnclosingClass.test(clazz)) {
+ descriptor = findAnnotationDescriptor(clazz.getEnclosingClass(), annotationType, searchEnclosingClass, visited);
if (descriptor != null) {
return descriptor;
}
@@ -301,6 +313,7 @@ public abstract class MetaAnnotationUtils {
* class should be searched
* @since 5.3
* @see ClassUtils#isInnerClass(Class)
+ * @see NestedTestConfiguration @NestedTestConfiguration
*/
public static boolean searchEnclosingClass(Class> clazz) {
return (ClassUtils.isInnerClass(clazz) &&
@@ -319,11 +332,13 @@ public abstract class MetaAnnotationUtils {
}
private static EnclosingConfiguration lookUpEnclosingConfiguration(Class> clazz) {
- return MergedAnnotations.from(clazz, SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES)
- .stream(NestedTestConfiguration.class)
- .map(mergedAnnotation -> mergedAnnotation.getEnum("value", EnclosingConfiguration.class))
- .findFirst()
- .orElseGet(MetaAnnotationUtils::getDefaultEnclosingConfigurationMode);
+ // @NestedTestConfiguration should not be discovered on an enclosing class
+ // for a nested interface (which is always static), so our predicate simply
+ // ensures that the candidate class is an inner class.
+ Predicate> searchEnclosingClass = ClassUtils::isInnerClass;
+ NestedTestConfiguration nestedTestConfiguration =
+ findMergedAnnotation(clazz, NestedTestConfiguration.class, searchEnclosingClass);
+ return (nestedTestConfiguration != null ? nestedTestConfiguration.value() : getDefaultEnclosingConfigurationMode());
}
private static EnclosingConfiguration getDefaultEnclosingConfigurationMode() {
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestPropertySourceNestedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestPropertySourceNestedTests.java
index fe42ec6d67..3d485974a0 100644
--- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestPropertySourceNestedTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestPropertySourceNestedTests.java
@@ -161,12 +161,6 @@ class TestPropertySourceNestedTests {
}
@Nested
- // The following explicit INHERIT is necessary since this nested
- // test class implements an interface whose enclosing class is
- // annotated with @NestedTestConfiguration(OVERRIDE). In other
- // words, the local declaration overrides the declaration
- // "inherited" via the interface.
- @NestedTestConfiguration(INHERIT)
class L5WithInheritedConfigAndTestInterfaceTests implements TestInterface {
@Autowired