diff --git a/spring-test/src/main/java/org/springframework/test/context/TestContextAnnotationUtils.java b/spring-test/src/main/java/org/springframework/test/context/TestContextAnnotationUtils.java index 2d6ace8f5d8..437b25a6d1f 100644 --- a/spring-test/src/main/java/org/springframework/test/context/TestContextAnnotationUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/TestContextAnnotationUtils.java @@ -17,10 +17,12 @@ package org.springframework.test.context; import java.lang.annotation.Annotation; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.function.Predicate; +import java.util.stream.Stream; import org.jspecify.annotations.Nullable; @@ -578,13 +580,14 @@ public abstract class TestContextAnnotationUtils { * or an empty set if none were found */ public Set findAllLocalMergedAnnotations() { - Class rootDeclaringClass = getRootDeclaringClass(); - Set> localSources = new HashSet<>(); - localSources.add(rootDeclaringClass); - Collections.addAll(localSources, rootDeclaringClass.getInterfaces()); - return MergedAnnotations.from(rootDeclaringClass, SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.none()) - .stream(getAnnotationType()) - .filter(mergedAnnotation -> localSources.contains(mergedAnnotation.getSource())) + Class annotationType = getAnnotationType(); + Stream> classAnnotations = + MergedAnnotations.from(this.rootDeclaringClass, SearchStrategy.DIRECT, RepeatableContainers.none()) + .stream(annotationType); + Stream> interfaceAnnotations = Arrays.stream(this.rootDeclaringClass.getInterfaces()) + .flatMap(ifc -> MergedAnnotations.from(ifc, SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.none()) + .stream(annotationType)); + return Stream.concat(classAnnotations, interfaceAnnotations) .collect(MergedAnnotationCollectors.toAnnotationSet()); } diff --git a/spring-test/src/test/java/org/springframework/test/context/TestContextAnnotationUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/TestContextAnnotationUtilsTests.java index d9f9a2bb067..08f950d96d5 100644 --- a/spring-test/src/test/java/org/springframework/test/context/TestContextAnnotationUtilsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/TestContextAnnotationUtilsTests.java @@ -296,6 +296,17 @@ class TestContextAnnotationUtilsTests { .satisfies(config -> assertThat(config.classes()).containsExactly(Config2.class)); } + @Test + void annotationOnTransitiveInterface() { + var descriptor = findAnnotationDescriptor(ClassImplementingTransitiveAnnotatedInterface.class, ContextConfiguration.class); + assertThat(descriptor).isNotNull(); + assertThat(descriptor.findAllLocalMergedAnnotations()) + .satisfiesExactly( + config1 -> assertThat(config1.classes()).containsExactly(Config1.class), + config2 -> assertThat(config2.classes()).containsExactly(Config2.class) + ); + } + @Test void annotationOnClassAndInterface() { var descriptor = findAnnotationDescriptor(AnnotatedClassImplementingAnnotatedInterface.class, ContextConfiguration.class); @@ -765,6 +776,13 @@ class TestContextAnnotationUtilsTests { static class ClassImplementingAnnotatedInterface implements AnnotatedInterface { } + interface TransitiveAnnotatedInterface extends AnnotatedInterface { + } + + @ContextConfiguration(classes = Config1.class) + static class ClassImplementingTransitiveAnnotatedInterface implements TransitiveAnnotatedInterface { + } + @ContextConfiguration(classes = Config1.class) static class AnnotatedClassImplementingAnnotatedInterface implements AnnotatedInterface { }