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 8292a3cd99..807c41acbe 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 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. @@ -16,16 +16,18 @@ package org.springframework.test.context; +import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; +import java.util.LinkedHashSet; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeanUtils; -import org.springframework.core.annotation.AnnotatedElementUtils; -import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.lang.Nullable; +import org.springframework.test.util.MetaAnnotationUtils; +import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor; import org.springframework.util.ClassUtils; /** @@ -56,6 +58,8 @@ abstract class BootstrapUtils { private static final String WEB_APP_CONFIGURATION_ANNOTATION_CLASS_NAME = "org.springframework.test.context.web.WebAppConfiguration"; + private static final Class webAppConfigurationClass = loadWebAppConfigurationClass(); + private static final Log logger = LogFactory.getLog(BootstrapUtils.class); @@ -149,7 +153,14 @@ abstract class BootstrapUtils { @Nullable private static Class resolveExplicitTestContextBootstrapper(Class testClass) { - Set annotations = AnnotatedElementUtils.findAllMergedAnnotations(testClass, BootstrapWith.class); + Set annotations = new LinkedHashSet<>(); + AnnotationDescriptor descriptor = + MetaAnnotationUtils.findAnnotationDescriptor(testClass, BootstrapWith.class); + while (descriptor != null) { + annotations.addAll(descriptor.findAllLocalMergedAnnotations()); + descriptor = descriptor.next(); + } + if (annotations.isEmpty()) { return null; } @@ -169,13 +180,22 @@ abstract class BootstrapUtils { } private static Class resolveDefaultTestContextBootstrapper(Class testClass) throws Exception { - ClassLoader classLoader = BootstrapUtils.class.getClassLoader(); - AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(testClass, - WEB_APP_CONFIGURATION_ANNOTATION_CLASS_NAME, false, false); - if (attributes != null) { - return ClassUtils.forName(DEFAULT_WEB_TEST_CONTEXT_BOOTSTRAPPER_CLASS_NAME, classLoader); + boolean webApp = (MetaAnnotationUtils.findMergedAnnotation(testClass, webAppConfigurationClass) != null); + String bootstrapperClassName = (webApp ? DEFAULT_WEB_TEST_CONTEXT_BOOTSTRAPPER_CLASS_NAME : + DEFAULT_TEST_CONTEXT_BOOTSTRAPPER_CLASS_NAME); + return ClassUtils.forName(bootstrapperClassName, BootstrapUtils.class.getClassLoader()); + } + + @SuppressWarnings("unchecked") + private static Class loadWebAppConfigurationClass() { + try { + return (Class) ClassUtils.forName(WEB_APP_CONFIGURATION_ANNOTATION_CLASS_NAME, + BootstrapUtils.class.getClassLoader()); + } + catch (ClassNotFoundException | LinkageError ex) { + throw new IllegalStateException( + "Failed to load class for @" + WEB_APP_CONFIGURATION_ANNOTATION_CLASS_NAME, ex); } - return ClassUtils.forName(DEFAULT_TEST_CONTEXT_BOOTSTRAPPER_CLASS_NAME, classLoader); } } 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 new file mode 100644 index 0000000000..4c7d42006e --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/NestedTestConfiguration.java @@ -0,0 +1,94 @@ +/* + * Copyright 2002-2020 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 + * + * https://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.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * {@code @NestedTestConfiguration} is a type-level annotation that is used to + * configure how Spring test configuration annotations are processed within + * enclosing class hierarchies (i.e., for inner test classes). + * + *

If {@code @NestedTestConfiguration} is not present or + * meta-present on a test class, configuration from the test class will + * not propagate to inner test classes (see {@link EnclosingConfiguration#OVERRIDE}). + * Consequently, inner test classes will have to declare their own Spring test + * configuration annotations. If you wish for an inner test class to inherit + * configuration from its enclosing class, annotate either the inner test class + * or the enclosing class with + * {@code @NestedTestConfiguration(EnclosingConfiguration.INHERIT)}. Note that + * a {@code @NestedTestConfiguration(...)} declaration is inherited within the + * superclass hierarchy as well as within the enclosing class hierarchy. Thus, + * there is no need to redeclare the annotation unless you wish to switch the + * mode. + * + *

This annotation may be used as a meta-annotation to create custom + * composed annotations. + * + *

As of Spring Framework 5.3, the use of this annotation typically only makes + * sense in conjunction with {@link org.junit.jupiter.api.Nested @Nested} test + * classes in JUnit Jupiter. + * + * @author Sam Brannen + * @since 5.3 + * @see EnclosingConfiguration#INHERIT + * @see EnclosingConfiguration#OVERRIDE + * @see ContextConfiguration @ContextConfiguration + * @see ContextHierarchy @ContextHierarchy + * @see ActiveProfiles @ActiveProfiles + * @see TestPropertySource @TestPropertySource + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface NestedTestConfiguration { + + /** + * Configures the {@link EnclosingConfiguration} mode. + */ + EnclosingConfiguration value(); + + + /** + * Enumeration of modes that dictate how test configuration from + * enclosing classes is processed for inner test classes. + */ + enum EnclosingConfiguration { + + /** + * Indicates that test configuration for an inner test class should be + * inherited from its {@linkplain Class#getEnclosingClass() + * enclosing class}, as if the enclosing class were a superclass. + */ + INHERIT, + + /** + * Indicates that test configuration for an inner test class should + * override configuration from its + * {@linkplain Class#getEnclosingClass() enclosing class}. + */ + OVERRIDE + + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractDirtiesContextTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractDirtiesContextTestExecutionListener.java index 84a4f0cf65..f274fc30c9 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractDirtiesContextTestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractDirtiesContextTestExecutionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2020 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. @@ -29,6 +29,7 @@ import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.annotation.DirtiesContext.HierarchyMode; import org.springframework.test.annotation.DirtiesContext.MethodMode; import org.springframework.test.context.TestContext; +import org.springframework.test.util.MetaAnnotationUtils; import org.springframework.util.Assert; /** @@ -96,7 +97,7 @@ public abstract class AbstractDirtiesContextTestExecutionListener extends Abstra Assert.notNull(testMethod, "The test method of the supplied TestContext must not be null"); DirtiesContext methodAnn = AnnotatedElementUtils.findMergedAnnotation(testMethod, DirtiesContext.class); - DirtiesContext classAnn = AnnotatedElementUtils.findMergedAnnotation(testClass, DirtiesContext.class); + DirtiesContext classAnn = MetaAnnotationUtils.findMergedAnnotation(testClass, DirtiesContext.class); boolean methodAnnotated = (methodAnn != null); boolean classAnnotated = (classAnn != null); MethodMode methodMode = (methodAnnotated ? methodAnn.methodMode() : null); @@ -133,7 +134,7 @@ public abstract class AbstractDirtiesContextTestExecutionListener extends Abstra Class testClass = testContext.getTestClass(); Assert.notNull(testClass, "The test class of the supplied TestContext must not be null"); - DirtiesContext dirtiesContext = AnnotatedElementUtils.findMergedAnnotation(testClass, DirtiesContext.class); + DirtiesContext dirtiesContext = MetaAnnotationUtils.findMergedAnnotation(testClass, DirtiesContext.class); boolean classAnnotated = (dirtiesContext != null); ClassMode classMode = (classAnnotated ? dirtiesContext.classMode() : null); diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java index 040578d7eb..21e3ca26b3 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 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. @@ -31,7 +31,6 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeanInstantiationException; import org.springframework.beans.BeanUtils; import org.springframework.core.annotation.AnnotationAwareOrderComparator; -import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.lang.Nullable; import org.springframework.test.context.BootstrapContext; @@ -139,13 +138,11 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot } boolean inheritListeners = testExecutionListeners.inheritListeners(); - AnnotationDescriptor superDescriptor = - MetaAnnotationUtils.findAnnotationDescriptor( - descriptor.getRootDeclaringClass().getSuperclass(), annotationType); + AnnotationDescriptor parentDescriptor = descriptor.next(); // If there are no listeners to inherit, we might need to merge the // locally declared listeners with the defaults. - if ((!inheritListeners || superDescriptor == null) && + if ((!inheritListeners || parentDescriptor == null) && testExecutionListeners.mergeMode() == MergeMode.MERGE_WITH_DEFAULTS) { if (logger.isDebugEnabled()) { logger.debug(String.format("Merging default listeners with listeners configured via " + @@ -157,7 +154,7 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot classesList.addAll(0, Arrays.asList(testExecutionListeners.listeners())); - descriptor = (inheritListeners ? superDescriptor : null); + descriptor = (inheritListeners ? parentDescriptor : null); } } @@ -265,7 +262,7 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot return buildDefaultMergedContextConfiguration(testClass, cacheAwareContextLoaderDelegate); } - if (AnnotationUtils.findAnnotation(testClass, ContextHierarchy.class) != null) { + if (MetaAnnotationUtils.findAnnotationDescriptor(testClass, ContextHierarchy.class) != null) { Map> hierarchyMap = ContextLoaderUtils.buildContextHierarchyMap(testClass); MergedContextConfiguration parentConfig = null; diff --git a/spring-test/src/main/java/org/springframework/test/context/support/ActiveProfilesUtils.java b/spring-test/src/main/java/org/springframework/test/context/support/ActiveProfilesUtils.java index f56d935f16..fbd827dfbb 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/ActiveProfilesUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/ActiveProfilesUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2020 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. @@ -28,12 +28,13 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeanUtils; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfilesResolver; -import org.springframework.test.util.MetaAnnotationUtils; import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; +import static org.springframework.test.util.MetaAnnotationUtils.findAnnotationDescriptor; + /** * Utility methods for working with {@link ActiveProfiles @ActiveProfiles} and * {@link ActiveProfilesResolver ActiveProfilesResolvers}. @@ -70,25 +71,21 @@ abstract class ActiveProfilesUtils { static String[] resolveActiveProfiles(Class testClass) { Assert.notNull(testClass, "Class must not be null"); - final List profileArrays = new ArrayList<>(); - - Class annotationType = ActiveProfiles.class; - AnnotationDescriptor descriptor = - MetaAnnotationUtils.findAnnotationDescriptor(testClass, annotationType); + List profileArrays = new ArrayList<>(); + AnnotationDescriptor descriptor = findAnnotationDescriptor(testClass, ActiveProfiles.class); if (descriptor == null && logger.isDebugEnabled()) { logger.debug(String.format( "Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]", - annotationType.getName(), testClass.getName())); + ActiveProfiles.class.getName(), testClass.getName())); } while (descriptor != null) { Class rootDeclaringClass = descriptor.getRootDeclaringClass(); - Class declaringClass = descriptor.getDeclaringClass(); ActiveProfiles annotation = descriptor.synthesizeAnnotation(); if (logger.isTraceEnabled()) { logger.trace(String.format("Retrieved @ActiveProfiles [%s] for declaring class [%s]", - annotation, declaringClass.getName())); + annotation, descriptor.getDeclaringClass().getName())); } Class resolverClass = annotation.resolver(); @@ -112,14 +109,13 @@ abstract class ActiveProfilesUtils { profileArrays.add(profiles); } - descriptor = (annotation.inheritProfiles() ? MetaAnnotationUtils.findAnnotationDescriptor( - rootDeclaringClass.getSuperclass(), annotationType) : null); + descriptor = (annotation.inheritProfiles() ? descriptor.next() : null); } // Reverse the list so that we can traverse "down" the hierarchy. Collections.reverse(profileArrays); - final Set activeProfiles = new LinkedHashSet<>(); + Set activeProfiles = new LinkedHashSet<>(); for (String[] profiles : profileArrays) { for (String profile : profiles) { if (StringUtils.hasText(profile)) { diff --git a/spring-test/src/main/java/org/springframework/test/context/support/ContextLoaderUtils.java b/spring-test/src/main/java/org/springframework/test/context/support/ContextLoaderUtils.java index 70d70d9f0c..75adfd45cb 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/ContextLoaderUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/ContextLoaderUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 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. @@ -150,8 +150,8 @@ abstract class ContextLoaderUtils { } hierarchyAttributes.add(0, configAttributesList); - desc = findAnnotationDescriptorForTypes( - rootDeclaringClass.getSuperclass(), contextConfigType, contextHierarchyType); + + desc = desc.next(); } return hierarchyAttributes; @@ -182,7 +182,7 @@ abstract class ContextLoaderUtils { * @see #resolveContextHierarchyAttributes(Class) */ static Map> buildContextHierarchyMap(Class testClass) { - final Map> map = new LinkedHashMap<>(); + Map> map = new LinkedHashMap<>(); int hierarchyLevel = 1; for (List configAttributesList : resolveContextHierarchyAttributes(testClass)) { @@ -237,30 +237,34 @@ abstract class ContextLoaderUtils { static List resolveContextConfigurationAttributes(Class testClass) { Assert.notNull(testClass, "Class must not be null"); - List attributesList = new ArrayList<>(); Class annotationType = ContextConfiguration.class; - AnnotationDescriptor descriptor = findAnnotationDescriptor(testClass, annotationType); Assert.notNull(descriptor, () -> String.format( "Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]", annotationType.getName(), testClass.getName())); - while (descriptor != null) { - convertContextConfigToConfigAttributesAndAddToList(descriptor.synthesizeAnnotation(), - descriptor.getRootDeclaringClass(), attributesList); - descriptor = findAnnotationDescriptor(descriptor.getRootDeclaringClass().getSuperclass(), annotationType); - } - + List attributesList = new ArrayList<>(); + resolveContextConfigurationAttributes(attributesList, descriptor); return attributesList; } + private static void resolveContextConfigurationAttributes(List attributesList, + AnnotationDescriptor descriptor) { + + if (descriptor != null) { + convertContextConfigToConfigAttributesAndAddToList(descriptor.synthesizeAnnotation(), + descriptor.getRootDeclaringClass(), attributesList); + resolveContextConfigurationAttributes(attributesList, descriptor.next()); + } + } + /** * Convenience method for creating a {@link ContextConfigurationAttributes} * instance from the supplied {@link ContextConfiguration} annotation and * declaring class and then adding the attributes to the supplied list. */ private static void convertContextConfigToConfigAttributesAndAddToList(ContextConfiguration contextConfiguration, - Class declaringClass, final List attributesList) { + Class declaringClass, List attributesList) { if (logger.isTraceEnabled()) { logger.trace(String.format("Retrieved @ContextConfiguration [%s] for declaring class [%s].", diff --git a/spring-test/src/main/java/org/springframework/test/context/support/MergedTestPropertySources.java b/spring-test/src/main/java/org/springframework/test/context/support/MergedTestPropertySources.java index ad520a007d..ee7e31d00e 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/MergedTestPropertySources.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/MergedTestPropertySources.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2020 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. @@ -16,6 +16,10 @@ package org.springframework.test.context.support; +import java.util.Arrays; + +import org.springframework.core.style.ToStringCreator; +import org.springframework.lang.Nullable; import org.springframework.test.context.TestPropertySource; import org.springframework.util.Assert; @@ -76,4 +80,55 @@ class MergedTestPropertySources { return this.properties; } + /** + * Determine if the supplied object is equal to this {@code MergedTestPropertySources} + * instance by comparing both object's {@linkplain #getLocations() locations} + * and {@linkplain #getProperties() properties}. + * @since 5.3 + */ + @Override + public boolean equals(@Nullable Object other) { + if (this == other) { + return true; + } + if (other == null || other.getClass() != getClass()) { + return false; + } + + MergedTestPropertySources that = (MergedTestPropertySources) other; + if (!Arrays.equals(this.locations, that.locations)) { + return false; + } + if (!Arrays.equals(this.properties, that.properties)) { + return false; + } + + return true; + } + + /** + * Generate a unique hash code for all properties of this + * {@code MergedTestPropertySources} instance. + * @since 5.3 + */ + @Override + public int hashCode() { + int result = Arrays.hashCode(this.locations); + result = 31 * result + Arrays.hashCode(this.properties); + return result; + } + + /** + * Provide a String representation of this {@code MergedTestPropertySources} + * instance. + * @since 5.3 + */ + @Override + public String toString() { + return new ToStringCreator(this) + .append("locations", Arrays.toString(this.locations)) + .append("properties", Arrays.toString(this.properties)) + .toString(); + } + } diff --git a/spring-test/src/main/java/org/springframework/test/context/support/TestConstructorUtils.java b/spring-test/src/main/java/org/springframework/test/context/support/TestConstructorUtils.java index 91e4aec5f4..0061b1c07a 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/TestConstructorUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/TestConstructorUtils.java @@ -25,6 +25,7 @@ import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.lang.Nullable; import org.springframework.test.context.TestConstructor; import org.springframework.test.context.TestConstructor.AutowireMode; +import org.springframework.test.util.MetaAnnotationUtils; /** * Utility methods for working with {@link TestConstructor @TestConstructor}. @@ -133,7 +134,7 @@ public abstract class TestConstructorUtils { AutowireMode autowireMode = null; // Is the test class annotated with @TestConstructor? - TestConstructor testConstructor = AnnotatedElementUtils.findMergedAnnotation(testClass, TestConstructor.class); + TestConstructor testConstructor = MetaAnnotationUtils.findMergedAnnotation(testClass, TestConstructor.class); if (testConstructor != null) { autowireMode = testConstructor.autowireMode(); } diff --git a/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceAttributes.java b/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceAttributes.java index fefe738340..52f14ae139 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceAttributes.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceAttributes.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -17,7 +17,7 @@ package org.springframework.test.context.support; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collections; import java.util.List; import org.apache.commons.logging.Log; @@ -25,7 +25,6 @@ import org.apache.commons.logging.LogFactory; import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.io.ClassPathResource; -import org.springframework.core.log.LogMessage; import org.springframework.core.style.ToStringCreator; import org.springframework.test.context.TestPropertySource; import org.springframework.util.Assert; @@ -52,12 +51,8 @@ class TestPropertySourceAttributes { private static final Log logger = LogFactory.getLog(TestPropertySourceAttributes.class); - private final int aggregateIndex; - private final Class declaringClass; - private final MergedAnnotation rootAnnotation; - private final List locations = new ArrayList<>(); private final boolean inheritLocations; @@ -68,96 +63,24 @@ class TestPropertySourceAttributes { TestPropertySourceAttributes(MergedAnnotation annotation) { - this.aggregateIndex = annotation.getAggregateIndex(); this.declaringClass = declaringClass(annotation); - this.rootAnnotation = annotation.getRoot(); this.inheritLocations = annotation.getBoolean("inheritLocations"); this.inheritProperties = annotation.getBoolean("inheritProperties"); mergePropertiesAndLocations(annotation); } - - /** - * Determine if the annotation represented by this - * {@code TestPropertySourceAttributes} instance can be merged with the - * supplied {@code annotation}. - *

This method effectively checks that two annotations are declared at - * the same level in the type hierarchy (i.e., have the same - * {@linkplain MergedAnnotation#getAggregateIndex() aggregate index}). - * @since 5.2 - * @see #mergeWith(MergedAnnotation) - */ - boolean canMergeWith(MergedAnnotation annotation) { - return annotation.getAggregateIndex() == this.aggregateIndex; - } - - /** - * Merge this {@code TestPropertySourceAttributes} instance with the - * supplied {@code annotation}, asserting that the two sets of test property - * source attributes have identical values for the - * {@link TestPropertySource#inheritLocations} and - * {@link TestPropertySource#inheritProperties} flags and that the two - * underlying annotations were declared on the same class. - *

This method should only be invoked if {@link #canMergeWith(MergedAnnotation)} - * returns {@code true}. - * @since 5.2 - * @see #canMergeWith(MergedAnnotation) - */ - void mergeWith(MergedAnnotation annotation) { - Class source = declaringClass(annotation); - Assert.state(source == this.declaringClass, - () -> "Detected @TestPropertySource declarations within an aggregate index " - + "with different sources: " + this.declaringClass.getName() + " and " - + source.getName()); - logger.trace(LogMessage.format("Retrieved %s for declaring class [%s].", - annotation, this.declaringClass.getName())); - assertSameBooleanAttribute(this.inheritLocations, annotation, "inheritLocations"); - assertSameBooleanAttribute(this.inheritProperties, annotation, "inheritProperties"); - mergePropertiesAndLocations(annotation); - } - - private void assertSameBooleanAttribute(boolean expected, MergedAnnotation annotation, - String attribute) { - - Assert.isTrue(expected == annotation.getBoolean(attribute), () -> String.format( - "@%s on %s and @%s on %s must declare the same value for '%s' as other " + - "directly present or meta-present @TestPropertySource annotations", - this.rootAnnotation.getType().getSimpleName(), this.declaringClass.getSimpleName(), - annotation.getRoot().getType().getSimpleName(), declaringClass(annotation).getSimpleName(), - attribute)); - } - private void mergePropertiesAndLocations(MergedAnnotation annotation) { String[] locations = annotation.getStringArray("locations"); String[] properties = annotation.getStringArray("properties"); - // If the meta-distance is positive, that means the annotation is - // meta-present and should therefore have lower priority than directly - // present annotations (i.e., it should be prepended to the list instead - // of appended). This follows the rule of last-one-wins for overriding - // properties. - boolean prepend = annotation.getDistance() > 0; if (ObjectUtils.isEmpty(locations) && ObjectUtils.isEmpty(properties)) { - addAll(prepend, this.locations, detectDefaultPropertiesFile(annotation)); + Collections.addAll(this.locations, detectDefaultPropertiesFile(annotation)); } else { - addAll(prepend, this.locations, locations); - addAll(prepend, this.properties, properties); + Collections.addAll(this.locations, locations); + Collections.addAll(this.properties, properties); } } - /** - * Add all of the supplied elements to the provided list, honoring the - * {@code prepend} flag. - *

If the {@code prepend} flag is {@code false}, the elements will appended - * to the list. - * @param prepend whether the elements should be prepended to the list - * @param list the list to which to add the elements - * @param elements the elements to add to the list - */ - private void addAll(boolean prepend, List list, String... elements) { - list.addAll((prepend ? 0 : list.size()), Arrays.asList(elements)); - } - private String detectDefaultPropertiesFile(MergedAnnotation annotation) { Class testClass = declaringClass(annotation); String resourcePath = ClassUtils.convertClassNameToResourcePath(testClass.getName()) + ".properties"; diff --git a/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceUtils.java b/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceUtils.java index 913715b951..e5e938633e 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -18,12 +18,15 @@ package org.springframework.test.context.support; import java.io.IOException; import java.io.StringReader; +import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; +import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.stream.Collectors; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -42,6 +45,7 @@ import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.ResourcePropertySource; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.util.TestContextResourceUtils; +import org.springframework.test.util.MetaAnnotationUtils; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -71,42 +75,22 @@ public abstract class TestPropertySourceUtils { static MergedTestPropertySources buildMergedTestPropertySources(Class testClass) { - MergedAnnotations mergedAnnotations = MergedAnnotations.from(testClass, SearchStrategy.TYPE_HIERARCHY); - return (mergedAnnotations.isPresent(TestPropertySource.class) ? mergeTestPropertySources(mergedAnnotations) : - MergedTestPropertySources.empty()); + return mergeTestPropertySources(findRepeatableAnnotations(testClass, TestPropertySource.class)); } - private static MergedTestPropertySources mergeTestPropertySources(MergedAnnotations mergedAnnotations) { - List attributesList = resolveTestPropertySourceAttributes(mergedAnnotations); + private static MergedTestPropertySources mergeTestPropertySources( + List> mergedAnnotations) { + + if (mergedAnnotations.isEmpty()) { + return MergedTestPropertySources.empty(); + } + + List attributesList = mergedAnnotations.stream() + .map(TestPropertySourceAttributes::new) + .collect(Collectors.toList()); return new MergedTestPropertySources(mergeLocations(attributesList), mergeProperties(attributesList)); } - private static List resolveTestPropertySourceAttributes( - MergedAnnotations mergedAnnotations) { - - List attributesList = new ArrayList<>(); - mergedAnnotations.stream(TestPropertySource.class) - .forEach(annotation -> addOrMergeTestPropertySourceAttributes(attributesList, annotation)); - return attributesList; - } - - private static void addOrMergeTestPropertySourceAttributes(List attributesList, - MergedAnnotation current) { - - if (attributesList.isEmpty()) { - attributesList.add(new TestPropertySourceAttributes(current)); - } - else { - TestPropertySourceAttributes previous = attributesList.get(attributesList.size() - 1); - if (previous.canMergeWith(current)) { - previous.mergeWith(current); - } - else { - attributesList.add(new TestPropertySourceAttributes(current)); - } - } - } - private static String[] mergeLocations(List attributesList) { List locations = new ArrayList<>(); for (TestPropertySourceAttributes attrs : attributesList) { @@ -292,4 +276,53 @@ public abstract class TestPropertySourceUtils { return map; } + private static List> findRepeatableAnnotations( + Class clazz, Class annotationType) { + + List>> listOfLists = new ArrayList<>(); + findRepeatableAnnotations(clazz, annotationType, listOfLists, new int[] {0}); + return listOfLists.stream().flatMap(List::stream).collect(Collectors.toList()); + } + + private static void findRepeatableAnnotations( + Class clazz, Class annotationType, List>> listOfLists, int[] aggregateIndex) { + + MergedAnnotations.from(clazz, SearchStrategy.DIRECT) + .stream(annotationType) + .sorted(highMetaDistancesFirst()) + .forEach(annotation -> { + List> current = null; + if (listOfLists.size() < aggregateIndex[0] + 1) { + current = new ArrayList<>(); + listOfLists.add(current); + } + else { + current = listOfLists.get(aggregateIndex[0]); + } + current.add(0, annotation); + }); + + aggregateIndex[0]++; + + // Declared on an interface? + for (Class ifc : clazz.getInterfaces()) { + findRepeatableAnnotations(ifc, annotationType, listOfLists, aggregateIndex); + } + + // Declared on a superclass? + Class superclass = clazz.getSuperclass(); + if (superclass != null & superclass != Object.class) { + findRepeatableAnnotations(superclass, annotationType, listOfLists, aggregateIndex); + } + + // Declared on an enclosing class of an inner class? + if (MetaAnnotationUtils.searchEnclosingClass(clazz)) { + findRepeatableAnnotations(clazz.getEnclosingClass(), annotationType, listOfLists, aggregateIndex); + } + } + + private static Comparator> highMetaDistancesFirst() { + return Comparator.> comparingInt(MergedAnnotation::getDistance).reversed(); + } + } diff --git a/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java index 220bb62b0a..107f050427 100644 --- a/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -36,6 +36,7 @@ import org.springframework.test.annotation.Commit; import org.springframework.test.annotation.Rollback; import org.springframework.test.context.TestContext; import org.springframework.test.context.support.AbstractTestExecutionListener; +import org.springframework.test.util.MetaAnnotationUtils; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; @@ -148,7 +149,27 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis private static final Log logger = LogFactory.getLog(TransactionalTestExecutionListener.class); // Do not require @Transactional test methods to be public. - protected final TransactionAttributeSource attributeSource = new AnnotationTransactionAttributeSource(false); + @SuppressWarnings("serial") + protected final TransactionAttributeSource attributeSource = new AnnotationTransactionAttributeSource(false) { + + @Override + protected TransactionAttribute findTransactionAttribute(Class clazz) { + // @Transactional present in inheritance hierarchy? + TransactionAttribute result = super.findTransactionAttribute(clazz); + if (result != null) { + return result; + } + // @Transactional present in enclosing class hierarchy? + return findTransactionAttributeInEnclosingClassHierarchy(clazz); + } + + private TransactionAttribute findTransactionAttributeInEnclosingClassHierarchy(Class clazz) { + if (MetaAnnotationUtils.searchEnclosingClass(clazz)) { + return findTransactionAttribute(clazz.getEnclosingClass()); + } + return null; + } + }; /** @@ -376,7 +397,7 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis */ protected final boolean isDefaultRollback(TestContext testContext) throws Exception { Class testClass = testContext.getTestClass(); - Rollback rollback = AnnotatedElementUtils.findMergedAnnotation(testClass, Rollback.class); + Rollback rollback = MetaAnnotationUtils.findMergedAnnotation(testClass, Rollback.class); boolean rollbackPresent = (rollback != null); if (rollbackPresent) { diff --git a/spring-test/src/main/java/org/springframework/test/context/web/WebTestContextBootstrapper.java b/spring-test/src/main/java/org/springframework/test/context/web/WebTestContextBootstrapper.java index 04917c90b7..e6ef700831 100644 --- a/spring-test/src/main/java/org/springframework/test/context/web/WebTestContextBootstrapper.java +++ b/spring-test/src/main/java/org/springframework/test/context/web/WebTestContextBootstrapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2020 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. @@ -16,11 +16,11 @@ package org.springframework.test.context.web; -import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.test.context.ContextLoader; import org.springframework.test.context.MergedContextConfiguration; import org.springframework.test.context.TestContextBootstrapper; import org.springframework.test.context.support.DefaultTestContextBootstrapper; +import org.springframework.test.util.MetaAnnotationUtils; /** * Web-specific implementation of the {@link TestContextBootstrapper} SPI. @@ -45,7 +45,7 @@ public class WebTestContextBootstrapper extends DefaultTestContextBootstrapper { */ @Override protected Class getDefaultContextLoaderClass(Class testClass) { - if (AnnotatedElementUtils.hasAnnotation(testClass, WebAppConfiguration.class)) { + if (getWebAppConfiguration(testClass) != null) { return WebDelegatingSmartContextLoader.class; } else { @@ -61,8 +61,7 @@ public class WebTestContextBootstrapper extends DefaultTestContextBootstrapper { */ @Override protected MergedContextConfiguration processMergedContextConfiguration(MergedContextConfiguration mergedConfig) { - WebAppConfiguration webAppConfiguration = - AnnotatedElementUtils.findMergedAnnotation(mergedConfig.getTestClass(), WebAppConfiguration.class); + WebAppConfiguration webAppConfiguration = getWebAppConfiguration(mergedConfig.getTestClass()); if (webAppConfiguration != null) { return new WebMergedContextConfiguration(mergedConfig, webAppConfiguration.value()); } @@ -71,4 +70,8 @@ public class WebTestContextBootstrapper extends DefaultTestContextBootstrapper { } } + private static WebAppConfiguration getWebAppConfiguration(Class testClass) { + return MetaAnnotationUtils.findMergedAnnotation(testClass, WebAppConfiguration.class); + } + } 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 d9c813cc6c..cee1b4d56b 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 @@ -23,15 +23,27 @@ import java.util.Set; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.core.annotation.MergedAnnotationCollectors; +import org.springframework.core.annotation.MergedAnnotationPredicates; +import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; +import org.springframework.core.annotation.RepeatableContainers; import org.springframework.core.style.ToStringCreator; import org.springframework.lang.Nullable; +import org.springframework.test.context.NestedTestConfiguration; +import org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ConcurrentLruCache; import org.springframework.util.ObjectUtils; /** * {@code MetaAnnotationUtils} is a collection of utility methods that complements * the standard support already available in {@link AnnotationUtils}. * + *

Mainly for internal use within the framework. + * *

Whereas {@code AnnotationUtils} provides utilities for getting or * finding an annotation, {@code MetaAnnotationUtils} goes a step further * by providing support for determining the root class on which an @@ -56,6 +68,34 @@ import org.springframework.util.ObjectUtils; */ public abstract class MetaAnnotationUtils { + private static final ConcurrentLruCache, SearchStrategy> cachedSearchStrategies = + new ConcurrentLruCache<>(32, MetaAnnotationUtils::lookUpSearchStrategy); + + + /** + * Find the first annotation of the specified {@code annotationType} within + * the annotation hierarchy above the supplied class, merge that + * annotation's attributes with matching attributes from annotations + * in lower levels of the annotation hierarchy, and synthesize the result back + * into an annotation of the specified {@code annotationType}. + *

In the context of this method, the term "above" means within the + * {@linkplain Class#getSuperclass() superclass} hierarchy or within the + * {@linkplain Class#getEnclosingClass() enclosing class} hierarchy of the + * supplied class. The enclosing class hierarchy will only be searched if + * appropriate. + * @param clazz the class to look for annotations on + * @param annotationType the type of annotation to look for + * @return the merged, synthesized {@code Annotation}, or {@code null} if not found + * @since 5.3 + * @see AnnotatedElementUtils#findMergedAnnotation(java.lang.reflect.AnnotatedElement, Class) + * @see #findAnnotationDescriptor(Class, Class) + */ + @Nullable + public static T findMergedAnnotation(Class clazz, Class annotationType) { + AnnotationDescriptor descriptor = findAnnotationDescriptor(clazz, annotationType); + return (descriptor != null ? descriptor.synthesizeAnnotation() : null); + } + /** * Find the {@link AnnotationDescriptor} for the supplied {@code annotationType} * on the supplied {@link Class}, traversing its annotations, interfaces, and @@ -123,7 +163,7 @@ public abstract class MetaAnnotationUtils { } } - // Declared on interface? + // Declared on an interface? for (Class ifc : clazz.getInterfaces()) { AnnotationDescriptor descriptor = findAnnotationDescriptor(ifc, visited, annotationType); if (descriptor != null) { @@ -133,7 +173,21 @@ public abstract class MetaAnnotationUtils { } // Declared on a superclass? - return findAnnotationDescriptor(clazz.getSuperclass(), visited, annotationType); + AnnotationDescriptor descriptor = + findAnnotationDescriptor(clazz.getSuperclass(), visited, annotationType); + if (descriptor != null) { + return descriptor; + } + + // Declared on an enclosing class of an inner class? + if (searchEnclosingClass(clazz)) { + descriptor = findAnnotationDescriptor(clazz.getEnclosingClass(), visited, annotationType); + if (descriptor != null) { + return descriptor; + } + } + + return null; } /** @@ -196,7 +250,7 @@ public abstract class MetaAnnotationUtils { // Declared locally? for (Class annotationType : annotationTypes) { if (AnnotationUtils.isAnnotationDeclaredLocally(annotationType, clazz)) { - return new UntypedAnnotationDescriptor(clazz, clazz.getAnnotation(annotationType)); + return new UntypedAnnotationDescriptor(clazz, clazz.getAnnotation(annotationType), annotationTypes); } } @@ -207,22 +261,75 @@ public abstract class MetaAnnotationUtils { composedAnnotation.annotationType(), visited, annotationTypes); if (descriptor != null) { return new UntypedAnnotationDescriptor(clazz, descriptor.getDeclaringClass(), - composedAnnotation, descriptor.getAnnotation()); + composedAnnotation, descriptor.getAnnotation(), annotationTypes); } } } - // Declared on interface? + // Declared on an interface? for (Class ifc : clazz.getInterfaces()) { UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes(ifc, visited, annotationTypes); if (descriptor != null) { return new UntypedAnnotationDescriptor(clazz, descriptor.getDeclaringClass(), - descriptor.getComposedAnnotation(), descriptor.getAnnotation()); + descriptor.getComposedAnnotation(), descriptor.getAnnotation(), annotationTypes); } } // Declared on a superclass? - return findAnnotationDescriptorForTypes(clazz.getSuperclass(), visited, annotationTypes); + UntypedAnnotationDescriptor descriptor = + findAnnotationDescriptorForTypes(clazz.getSuperclass(), visited, annotationTypes); + if (descriptor != null) { + return descriptor; + } + + // Declared on an enclosing class of an inner class? + if (searchEnclosingClass(clazz)) { + descriptor = findAnnotationDescriptorForTypes(clazz.getEnclosingClass(), visited, annotationTypes); + if (descriptor != null) { + return descriptor; + } + } + + return null; + } + + /** + * Determine if annotations on the enclosing class of the supplied class + * should be searched by algorithms in {@link MetaAnnotationUtils}. + * @param clazz the class whose enclosing class should potentially be searched + * @return {@code true} if the supplied class is an inner class whose enclosing + * class should be searched + * @since 5.3 + * @see ClassUtils#isInnerClass(Class) + * @see #getSearchStrategy(Class) + */ + public static boolean searchEnclosingClass(Class clazz) { + return (ClassUtils.isInnerClass(clazz) && + getSearchStrategy(clazz) == SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES); + } + + /** + * Get the {@link SearchStrategy} for the supplied class. + * @param clazz the class for which the search strategy should be resolved + * @return the resolved search strategy + * @since 5.3 + */ + private static SearchStrategy getSearchStrategy(Class clazz) { + return cachedSearchStrategies.get(clazz); + } + + private static SearchStrategy lookUpSearchStrategy(Class clazz) { + EnclosingConfiguration enclosingConfiguration = + MergedAnnotations.from(clazz, SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES) + .stream(NestedTestConfiguration.class) + .map(mergedAnnotation -> mergedAnnotation.getEnum("value", EnclosingConfiguration.class)) + .findFirst() + .orElse(EnclosingConfiguration.OVERRIDE); + // TODO Switch the default EnclosingConfiguration mode to INHERIT. + // TODO Make the default EnclosingConfiguration mode globally configurable via SpringProperties. + return (enclosingConfiguration == EnclosingConfiguration.INHERIT ? + SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES : + SearchStrategy.TYPE_HIERARCHY); } private static void assertNonEmptyAnnotationTypeArray(Class[] annotationTypes, String message) { @@ -358,6 +465,54 @@ public abstract class MetaAnnotationUtils { return (this.composedAnnotation != null ? this.composedAnnotation.annotationType() : null); } + /** + * Find the next {@link AnnotationDescriptor} for the specified + * {@linkplain #getAnnotationType() annotation type} in the hierarchy + * above the {@linkplain #getRootDeclaringClass() root declaring class} + * of this descriptor. + *

If a corresponding annotation is found in the superclass hierarchy + * of the root declaring class, that will be returned. Otherwise, an + * attempt will be made to find a corresponding annotation in the + * {@linkplain Class#getEnclosingClass() enclosing class} hierarchy of + * the root declaring class if + * {@linkplain MetaAnnotationUtils#searchEnclosingClass appropriate}. + * @return the next corresponding annotation descriptor if the annotation + * was found; otherwise {@code null} + * @since 5.3 + */ + @Nullable + @SuppressWarnings("unchecked") + public AnnotationDescriptor next() { + Class annotationType = (Class) getAnnotationType(); + // Declared on a superclass? + AnnotationDescriptor descriptor = + findAnnotationDescriptor(getRootDeclaringClass().getSuperclass(), annotationType); + // Declared on an enclosing class of an inner class? + if (descriptor == null && searchEnclosingClass(getRootDeclaringClass())) { + descriptor = findAnnotationDescriptor(getRootDeclaringClass().getEnclosingClass(), annotationType); + } + return descriptor; + } + + /** + * Find all annotations of the specified + * {@linkplain #getAnnotationType() annotation type} that are present or + * meta-present on the {@linkplain #getRootDeclaringClass() root declaring + * class} of this descriptor. + * @return the set of all merged, synthesized {@code Annotations} found, + * or an empty set if none were found + * @since 5.3 + */ + @SuppressWarnings("unchecked") + public Set findAllLocalMergedAnnotations() { + Class annotationType = (Class) getAnnotationType(); + SearchStrategy searchStrategy = getSearchStrategy(getRootDeclaringClass()); + return MergedAnnotations.from(getRootDeclaringClass(), searchStrategy, RepeatableContainers.none()) + .stream(annotationType) + .filter(MergedAnnotationPredicates.firstRunOf(MergedAnnotation::getAggregateIndex)) + .collect(MergedAnnotationCollectors.toAnnotationSet()); + } + /** * Provide a textual representation of this {@code AnnotationDescriptor}. */ @@ -380,20 +535,48 @@ public abstract class MetaAnnotationUtils { */ public static class UntypedAnnotationDescriptor extends AnnotationDescriptor { + @Nullable + private final Class[] annotationTypes; + + /** + * Create a new {@plain UntypedAnnotationDescriptor}. + * @deprecated As of Spring Framework 5.3, in favor of + * {@link UntypedAnnotationDescriptor#UntypedAnnotationDescriptor(Class, Annotation, Class[])} + */ + @Deprecated public UntypedAnnotationDescriptor(Class rootDeclaringClass, Annotation annotation) { - this(rootDeclaringClass, rootDeclaringClass, null, annotation); + this(rootDeclaringClass, annotation, null); } + public UntypedAnnotationDescriptor(Class rootDeclaringClass, Annotation annotation, + @Nullable Class[] annotationTypes) { + + this(rootDeclaringClass, rootDeclaringClass, null, annotation, annotationTypes); + } + + /** + * Create a new {@plain UntypedAnnotationDescriptor}. + * @deprecated As of Spring Framework 5.3, in favor of + * {@link UntypedAnnotationDescriptor#UntypedAnnotationDescriptor(Class, Class, Annotation, Annotation, Class[])} + */ + @Deprecated public UntypedAnnotationDescriptor(Class rootDeclaringClass, Class declaringClass, @Nullable Annotation composedAnnotation, Annotation annotation) { + this(rootDeclaringClass, declaringClass, composedAnnotation, annotation, null); + } + + public UntypedAnnotationDescriptor(Class rootDeclaringClass, Class declaringClass, + @Nullable Annotation composedAnnotation, Annotation annotation, + @Nullable Class[] annotationTypes) { + super(rootDeclaringClass, declaringClass, composedAnnotation, annotation); + this.annotationTypes = annotationTypes; } /** * Throws an {@link UnsupportedOperationException} since the type of annotation - * represented by the {@link #getAnnotationAttributes AnnotationAttributes} in - * an {@code UntypedAnnotationDescriptor} is unknown. + * represented by an {@code UntypedAnnotationDescriptor} is unknown. * @since 4.2 */ @Override @@ -401,6 +584,52 @@ public abstract class MetaAnnotationUtils { throw new UnsupportedOperationException( "synthesizeAnnotation() is unsupported in UntypedAnnotationDescriptor"); } + + /** + * Find the next {@link UntypedAnnotationDescriptor} for the specified + * annotation types in the hierarchy above the + * {@linkplain #getRootDeclaringClass() root declaring class} of this + * descriptor. + *

If one of the corresponding annotations is found in the superclass + * hierarchy of the root declaring class, that will be returned. Otherwise, + * an attempt will be made to find a corresponding annotation in the + * {@linkplain Class#getEnclosingClass() enclosing class} hierarchy of + * the root declaring class if + * {@linkplain MetaAnnotationUtils#searchEnclosingClass appropriate}. + * @return the next corresponding annotation descriptor if one of the + * annotations was found; otherwise {@code null} + * @since 5.3 + * @see AnnotationDescriptor#next() + */ + @Override + @Nullable + public UntypedAnnotationDescriptor next() { + if (ObjectUtils.isEmpty(this.annotationTypes)) { + throw new UnsupportedOperationException( + "next() is unsupported if UntypedAnnotationDescriptor is instantiated without 'annotationTypes'"); + } + + // Declared on a superclass? + UntypedAnnotationDescriptor descriptor = + findAnnotationDescriptorForTypes(getRootDeclaringClass().getSuperclass(), this.annotationTypes); + // Declared on an enclosing class of an inner class? + if (descriptor == null && searchEnclosingClass(getRootDeclaringClass())) { + descriptor = findAnnotationDescriptorForTypes(getRootDeclaringClass().getEnclosingClass(), this.annotationTypes); + } + return descriptor; + } + + /** + * Throws an {@link UnsupportedOperationException} since the type of annotation + * represented by an {@code UntypedAnnotationDescriptor} is unknown. + * @since 5.3 + */ + @Override + public Set findAllLocalMergedAnnotations() { + throw new UnsupportedOperationException( + "findAllLocalMergedAnnotations() is unsupported in UntypedAnnotationDescriptor"); + } + } } 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 index b2cca6ae2a..7ed75786f8 100644 --- a/spring-test/src/test/java/org/springframework/test/context/BootstrapUtilsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/BootstrapUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -18,17 +18,33 @@ package org.springframework.test.context; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.stream.Stream; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.test.context.BootstrapUtilsTests.OuterClass.NestedWithInheritedBootstrapper; +import org.springframework.test.context.BootstrapUtilsTests.OuterClass.NestedWithInheritedBootstrapper.DoubleNestedWithInheritedButOverriddenBootstrapper; +import org.springframework.test.context.BootstrapUtilsTests.OuterClass.NestedWithInheritedBootstrapper.DoubleNestedWithOverriddenBootstrapper; +import org.springframework.test.context.BootstrapUtilsTests.OuterClass.NestedWithInheritedBootstrapper.DoubleNestedWithOverriddenBootstrapper.TripleNestedWithInheritedBootstrapper; +import org.springframework.test.context.BootstrapUtilsTests.OuterClass.NestedWithInheritedBootstrapper.DoubleNestedWithOverriddenBootstrapper.TripleNestedWithInheritedBootstrapperButLocalOverride; +import org.springframework.test.context.BootstrapUtilsTests.WebAppConfigClass.NestedWithInheritedWebConfig; +import org.springframework.test.context.BootstrapUtilsTests.WebAppConfigClass.NestedWithInheritedWebConfig.DoubleNestedWithImplicitlyInheritedWebConfig; +import org.springframework.test.context.BootstrapUtilsTests.WebAppConfigClass.NestedWithInheritedWebConfig.DoubleNestedWithOverriddenWebConfig; +import org.springframework.test.context.BootstrapUtilsTests.WebAppConfigClass.NestedWithInheritedWebConfig.DoubleNestedWithOverriddenWebConfig.TripleNestedWithInheritedOverriddenWebConfig; +import org.springframework.test.context.BootstrapUtilsTests.WebAppConfigClass.NestedWithInheritedWebConfig.DoubleNestedWithOverriddenWebConfig.TripleNestedWithInheritedOverriddenWebConfigAndTestInterface; import org.springframework.test.context.support.DefaultTestContextBootstrapper; -import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.context.web.WebTestContextBootstrapper; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.mockito.Mockito.mock; import static org.springframework.test.context.BootstrapUtils.resolveTestContextBootstrapper; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.INHERIT; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.OVERRIDE; /** * Unit tests for {@link BootstrapUtils}. @@ -65,11 +81,6 @@ class BootstrapUtilsTests { assertBootstrapper(NonAnnotatedClass.class, DefaultTestContextBootstrapper.class); } - @Test - void resolveTestContextBootstrapperForWebAppConfigurationAnnotatedClass() { - assertBootstrapper(WebAppConfigurationAnnotatedClass.class, WebTestContextBootstrapper.class); - } - @Test void resolveTestContextBootstrapperWithDirectBootstrapWithAnnotation() { assertBootstrapper(DirectBootstrapWithAnnotationClass.class, FooBootstrapper.class); @@ -90,6 +101,37 @@ class BootstrapUtilsTests { assertBootstrapper(DuplicateMetaAnnotatedBootstrapWithAnnotationClass.class, FooBootstrapper.class); } + /** + * @since 5.3 + */ + @ParameterizedTest(name = "{0}") + @MethodSource + void resolveTestContextBootstrapperInEnclosingClassHierarchy(String name, Class testClass, Class expectedBootstrapper) { + assertBootstrapper(testClass, expectedBootstrapper); + } + + static Stream resolveTestContextBootstrapperInEnclosingClassHierarchy() { + return Stream.of(// + args(OuterClass.class, FooBootstrapper.class),// + args(NestedWithInheritedBootstrapper.class, FooBootstrapper.class),// + args(DoubleNestedWithInheritedButOverriddenBootstrapper.class, EnigmaBootstrapper.class),// + args(DoubleNestedWithOverriddenBootstrapper.class, BarBootstrapper.class),// + args(TripleNestedWithInheritedBootstrapper.class, BarBootstrapper.class),// + args(TripleNestedWithInheritedBootstrapperButLocalOverride.class, EnigmaBootstrapper.class),// + // @WebAppConfiguration and default bootstrapper + args(WebAppConfigClass.class, WebTestContextBootstrapper.class),// + args(NestedWithInheritedWebConfig.class, WebTestContextBootstrapper.class),// + args(DoubleNestedWithImplicitlyInheritedWebConfig.class, WebTestContextBootstrapper.class),// + args(DoubleNestedWithOverriddenWebConfig.class, DefaultTestContextBootstrapper.class),// + args(TripleNestedWithInheritedOverriddenWebConfig.class, WebTestContextBootstrapper.class),// + args(TripleNestedWithInheritedOverriddenWebConfigAndTestInterface.class, DefaultTestContextBootstrapper.class)// + ); + } + + private static Arguments args(Class testClass, Class expectedBootstrapper) { + return arguments(testClass.getSimpleName(), testClass, expectedBootstrapper); + } + /** * @since 5.1 */ @@ -153,7 +195,62 @@ class BootstrapUtilsTests { @BootstrapWith(EnigmaBootstrapper.class) static class LocalDeclarationAndMetaAnnotatedBootstrapWithAnnotationClass {} - @WebAppConfiguration - static class WebAppConfigurationAnnotatedClass {} + @org.springframework.test.context.web.WebAppConfiguration + static class WebAppConfigClass { + + @NestedTestConfiguration(INHERIT) + class NestedWithInheritedWebConfig { + + class DoubleNestedWithImplicitlyInheritedWebConfig { + } + + @NestedTestConfiguration(OVERRIDE) + class DoubleNestedWithOverriddenWebConfig { + + @NestedTestConfiguration(INHERIT) + @org.springframework.test.context.web.WebAppConfiguration + class TripleNestedWithInheritedOverriddenWebConfig { + } + + @NestedTestConfiguration(INHERIT) + class TripleNestedWithInheritedOverriddenWebConfigAndTestInterface { + } + } + } + + // Intentionally not annotated with @WebAppConfiguration to ensure that + // TripleNestedWithInheritedOverriddenWebConfigAndTestInterface is not + // considered to be annotated with @WebAppConfiguration even though the + // enclosing class for TestInterface is annotated with @WebAppConfiguration. + interface TestInterface { + } + } + + @BootWithFoo + static class OuterClass { + + @NestedTestConfiguration(INHERIT) + class NestedWithInheritedBootstrapper { + + @NestedTestConfiguration(INHERIT) + @BootstrapWith(EnigmaBootstrapper.class) + class DoubleNestedWithInheritedButOverriddenBootstrapper { + } + + @NestedTestConfiguration(OVERRIDE) + @BootWithBar + class DoubleNestedWithOverriddenBootstrapper { + + @NestedTestConfiguration(INHERIT) + class TripleNestedWithInheritedBootstrapper { + } + + @NestedTestConfiguration(INHERIT) + @BootstrapWith(EnigmaBootstrapper.class) + class TripleNestedWithInheritedBootstrapperButLocalOverride { + } + } + } + } } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ActiveProfilesNestedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ActiveProfilesNestedTests.java new file mode 100644 index 0000000000..2a999775ef --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ActiveProfilesNestedTests.java @@ -0,0 +1,211 @@ +/* + * Copyright 2002-2020 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 + * + * https://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.junit.jupiter.nested; + +import java.util.List; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.NestedTestConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.springframework.test.context.junit.jupiter.nested.ActiveProfilesNestedTests.Config1; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.INHERIT; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.OVERRIDE; + +/** + * Integration tests that verify support for {@code @Nested} test classes using + * {@link ActiveProfiles @ActiveProfiles} in conjunction with the + * {@link SpringExtension} in a JUnit Jupiter environment. + * + * @author Sam Brannen + * @since 5.3 + */ +@SpringJUnitConfig(Config1.class) +@ActiveProfiles("1") +class ActiveProfilesNestedTests { + + @Autowired + List strings; + + + @Test + void test() { + assertThat(this.strings).containsExactlyInAnyOrder("X", "A1"); + } + + + @Nested + @NestedTestConfiguration(INHERIT) + class InheritedConfigTests { + + @Autowired + List localStrings; + + + @Test + void test() { + assertThat(strings).containsExactlyInAnyOrder("X", "A1"); + assertThat(this.localStrings).containsExactlyInAnyOrder("X", "A1"); + } + } + + @Nested + @SpringJUnitConfig(Config2.class) + @ActiveProfiles("2") + class ConfigOverriddenByDefaultTests { + + @Autowired + List localStrings; + + + @Test + void test() { + assertThat(strings).containsExactlyInAnyOrder("X", "A1"); + assertThat(this.localStrings).containsExactlyInAnyOrder("Y", "A2"); + } + } + + @Nested + @NestedTestConfiguration(INHERIT) + @ContextConfiguration(classes = Config2.class) + @ActiveProfiles("2") + class InheritedAndExtendedConfigTests { + + @Autowired + List localStrings; + + + @Test + void test() { + assertThat(strings).containsExactlyInAnyOrder("X", "A1"); + assertThat(this.localStrings).containsExactlyInAnyOrder("X", "A1", "Y", "A2"); + } + + + @Nested + @NestedTestConfiguration(OVERRIDE) + @SpringJUnitConfig({ Config1.class, Config2.class, Config3.class }) + @ActiveProfiles("3") + class DoubleNestedWithOverriddenConfigTests { + + @Autowired + List localStrings; + + + @Test + void test() { + assertThat(strings).containsExactlyInAnyOrder("X", "A1"); + assertThat(this.localStrings).containsExactlyInAnyOrder("X", "Y", "Z", "A3"); + } + + + @Nested + @NestedTestConfiguration(INHERIT) + @ActiveProfiles(profiles = "2", inheritProfiles = false) + class TripleNestedWithInheritedConfigButOverriddenProfilesTests { + + @Autowired + List localStrings; + + + @Test + void test() { + assertThat(strings).containsExactlyInAnyOrder("X", "A1"); + assertThat(this.localStrings).containsExactlyInAnyOrder("X", "Y", "Z", "A2"); + } + } + + @Nested + @NestedTestConfiguration(INHERIT) + class TripleNestedWithInheritedConfigAndTestInterfaceTests implements TestInterface { + + @Autowired + List localStrings; + + + @Test + void test() { + assertThat(strings).containsExactlyInAnyOrder("X", "A1"); + assertThat(this.localStrings).containsExactlyInAnyOrder("X", "Y", "Z", "A2", "A3"); + } + } + } + + } + + // ------------------------------------------------------------------------- + + @Configuration + static class Config1 { + + @Bean + String x() { + return "X"; + } + + @Bean + @Profile("1") + String a1() { + return "A1"; + } + } + + @Configuration + static class Config2 { + + @Bean + String y() { + return "Y"; + } + + @Bean + @Profile("2") + String a2() { + return "A2"; + } + } + + @Configuration + static class Config3 { + + @Bean + String z() { + return "Z"; + } + + @Bean + @Profile("3") + String a3() { + return "A3"; + } + } + + @ActiveProfiles("2") + interface TestInterface { + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/NestedTestsWithConstructorInjectionWithSpringAndJUnitJupiterTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ConstructorInjectionNestedTests.java similarity index 90% rename from spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/NestedTestsWithConstructorInjectionWithSpringAndJUnitJupiterTests.java rename to spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ConstructorInjectionNestedTests.java index 6478eca91b..c0decfb517 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/NestedTestsWithConstructorInjectionWithSpringAndJUnitJupiterTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ConstructorInjectionNestedTests.java @@ -28,7 +28,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.test.context.junit.SpringJUnitJupiterTestSuite; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; -import org.springframework.test.context.junit.jupiter.nested.NestedTestsWithConstructorInjectionWithSpringAndJUnitJupiterTests.TopLevelConfig; +import org.springframework.test.context.junit.jupiter.nested.ConstructorInjectionNestedTests.TopLevelConfig; import static org.assertj.core.api.Assertions.assertThat; @@ -43,15 +43,15 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Sam Brannen * @since 5.0.5 - * @see NestedTestsWithSpringAndJUnitJupiterTests + * @see ContextConfigurationNestedTests * @see org.springframework.test.context.junit4.nested.NestedTestsWithSpringRulesTests */ @SpringJUnitConfig(TopLevelConfig.class) -class NestedTestsWithConstructorInjectionWithSpringAndJUnitJupiterTests { +class ConstructorInjectionNestedTests { final String foo; - NestedTestsWithConstructorInjectionWithSpringAndJUnitJupiterTests(TestInfo testInfo, @Autowired String foo) { + ConstructorInjectionNestedTests(TestInfo testInfo, @Autowired String foo) { this.foo = foo; } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ContextConfigurationNestedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ContextConfigurationNestedTests.java new file mode 100644 index 0000000000..80770ba7e4 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ContextConfigurationNestedTests.java @@ -0,0 +1,212 @@ +/* + * Copyright 2002-2020 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 + * + * https://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.junit.jupiter.nested; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.NestedTestConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.springframework.test.context.junit.jupiter.nested.ContextConfigurationNestedTests.TopLevelConfig; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.INHERIT; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.OVERRIDE; + +/** + * Integration tests that verify support for {@code @Nested} test classes using + * {@link ContextConfiguration @ContextConfiguration} in conjunction with the + * {@link SpringExtension} in a JUnit Jupiter environment. + * + * @author Sam Brannen + * @since 5.0 + * @see ConstructorInjectionNestedTests + * @see org.springframework.test.context.junit4.nested.NestedTestsWithSpringRulesTests + */ +@SpringJUnitConfig(TopLevelConfig.class) +class ContextConfigurationNestedTests { + + private static final String FOO = "foo"; + private static final String BAR = "bar"; + private static final String BAZ = "baz"; + + @Autowired + String foo; + + + @Test + void topLevelTest() { + assertThat(foo).isEqualTo(FOO); + } + + + @Nested + @SpringJUnitConfig(NestedConfig.class) + class NestedTests { + + @Autowired(required = false) + @Qualifier("foo") + String localFoo; + + @Autowired + String bar; + + + @Test + void test() { + // In contrast to nested test classes running in JUnit 4, the foo + // field in the outer instance should have been injected from the + // test ApplicationContext for the outer instance. + assertThat(foo).isEqualTo(FOO); + assertThat(this.localFoo).as("foo bean should not be present").isNull(); + assertThat(this.bar).isEqualTo(BAR); + } + } + + @Nested + @NestedTestConfiguration(INHERIT) + class NestedTestCaseWithInheritedConfigTests { + + @Autowired(required = false) + @Qualifier("foo") + String localFoo; + + @Autowired + String bar; + + + @Test + void test() { + // Since the configuration is inherited, the foo field in the outer instance + // and the bar field in the inner instance should both have been injected + // from the test ApplicationContext for the outer instance. + assertThat(foo).isEqualTo(FOO); + assertThat(this.localFoo).isEqualTo(FOO); + assertThat(this.bar).isEqualTo(FOO); + } + + + @Nested + @NestedTestConfiguration(OVERRIDE) + @SpringJUnitConfig(NestedConfig.class) + class DoubleNestedWithOverriddenConfigTests { + + @Autowired(required = false) + @Qualifier("foo") + String localFoo; + + @Autowired + String bar; + + + @Test + void test() { + // In contrast to nested test classes running in JUnit 4, the foo + // field in the outer instance should have been injected from the + // test ApplicationContext for the outer instance. + assertThat(foo).isEqualTo(FOO); + assertThat(this.localFoo).as("foo bean should not be present").isNull(); + assertThat(this.bar).isEqualTo(BAR); + } + + + @Nested + @NestedTestConfiguration(INHERIT) + class TripleNestedWithInheritedConfigTests { + + @Autowired(required = false) + @Qualifier("foo") + String localFoo; + + @Autowired + String bar; + + + @Test + void test() { + assertThat(foo).isEqualTo(FOO); + assertThat(this.localFoo).as("foo bean should not be present").isNull(); + assertThat(this.bar).isEqualTo(BAR); + } + } + + @Nested + @NestedTestConfiguration(INHERIT) + class TripleNestedWithInheritedConfigAndTestInterfaceTests implements TestInterface { + + @Autowired(required = false) + @Qualifier("foo") + String localFoo; + + @Autowired + String bar; + + @Autowired + String baz; + + + @Test + void test() { + assertThat(foo).isEqualTo(FOO); + assertThat(this.localFoo).as("foo bean should not be present").isNull(); + assertThat(this.bar).isEqualTo(BAR); + assertThat(this.baz).isEqualTo(BAZ); + } + } + } + } + + // ------------------------------------------------------------------------- + + @Configuration + static class TopLevelConfig { + + @Bean + String foo() { + return FOO; + } + } + + @Configuration + static class NestedConfig { + + @Bean + String bar() { + return BAR; + } + } + + @Configuration + static class TestInterfaceConfig { + + @Bean + String baz() { + return BAZ; + } + } + + @ContextConfiguration(classes = TestInterfaceConfig.class) + interface TestInterface { + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ContextHierarchyNestedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ContextHierarchyNestedTests.java new file mode 100644 index 0000000000..909af00a37 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ContextHierarchyNestedTests.java @@ -0,0 +1,235 @@ +/* + * Copyright 2002-2020 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 + * + * https://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.junit.jupiter.nested; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.ContextHierarchy; +import org.springframework.test.context.NestedTestConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.context.junit.jupiter.nested.ContextHierarchyNestedTests.ParentConfig; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.INHERIT; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.OVERRIDE; + +/** + * Integration tests that verify support for {@code @Nested} test classes using + * {@link ContextHierarchy @ContextHierarchy} in conjunction with the + * {@link SpringExtension} in a JUnit Jupiter environment. + * + * @author Sam Brannen + * @since 5.3 + */ +@ExtendWith(SpringExtension.class) +@ContextHierarchy(@ContextConfiguration(classes = ParentConfig.class)) +class ContextHierarchyNestedTests { + + private static final String FOO = "foo"; + private static final String BAR = "bar"; + private static final String BAZ = "baz"; + private static final String QUX = "qux"; + + @Autowired + String foo; + + @Autowired + ApplicationContext context; + + + @Test + void topLevelTest() { + assertThat(this.context).as("local ApplicationContext").isNotNull(); + assertThat(this.context.getParent()).as("parent ApplicationContext").isNull(); + + assertThat(foo).isEqualTo(FOO); + } + + @Nested + @ContextConfiguration(classes = NestedConfig.class) + class NestedTests { + + @Autowired + String bar; + + @Autowired + ApplicationContext context; + + + @Test + void nestedTest() throws Exception { + assertThat(this.context).as("local ApplicationContext").isNotNull(); + assertThat(this.context.getParent()).as("parent ApplicationContext").isNull(); + + // In contrast to nested test classes running in JUnit 4, the foo + // field in the outer instance should have been injected from the + // test ApplicationContext for the outer instance. + assertThat(foo).isEqualTo(FOO); + assertThat(this.bar).isEqualTo(BAR); + } + } + + @Nested + @NestedTestConfiguration(INHERIT) + @ContextConfiguration(classes = Child1Config.class) + class NestedTestCaseWithInheritedConfigTests { + + @Autowired + String bar; + + @Autowired + ApplicationContext context; + + + @Test + void nestedTest() throws Exception { + assertThat(this.context).as("local ApplicationContext").isNotNull(); + assertThat(this.context.getParent()).as("parent ApplicationContext").isNotNull(); + + // Since the configuration is inherited, the foo field in the outer instance + // and the bar field in the inner instance should both have been injected + // from the test ApplicationContext for the outer instance. + assertThat(foo).isEqualTo(FOO); + assertThat(this.bar).isEqualTo(BAZ + 1); + assertThat(this.context.getBean("foo", String.class)).as("child foo").isEqualTo(QUX + 1); + } + + @Nested + @NestedTestConfiguration(OVERRIDE) + @ContextHierarchy({ + @ContextConfiguration(classes = ParentConfig.class), + @ContextConfiguration(classes = Child2Config.class) + }) + class DoubleNestedTestCaseWithOverriddenConfigTests { + + @Autowired + String bar; + + @Autowired + ApplicationContext context; + + + @Test + void nestedTest() throws Exception { + assertThat(this.context).as("local ApplicationContext").isNotNull(); + assertThat(this.context.getParent()).as("parent ApplicationContext").isNotNull(); + + assertThat(foo).isEqualTo(FOO); + assertThat(this.bar).isEqualTo(BAZ + 2); + assertThat(this.context.getBean("foo", String.class)).as("child foo").isEqualTo(QUX + 2); + } + + @Nested + @NestedTestConfiguration(INHERIT) + class TripleNestedWithInheritedConfigAndTestInterfaceTests implements TestInterface { + + @Autowired + @Qualifier("foo") + String localFoo; + + @Autowired + String bar; + + @Autowired + ApplicationContext context; + + + @Test + void nestedTest() throws Exception { + assertThat(this.context).as("local ApplicationContext").isNotNull(); + assertThat(this.context.getParent()).as("parent ApplicationContext").isNotNull(); + assertThat(this.context.getParent().getParent()).as("grandparent ApplicationContext").isNotNull(); + + assertThat(foo).isEqualTo(FOO); + assertThat(this.localFoo).isEqualTo("test interface"); + assertThat(this.bar).isEqualTo(BAZ + 2); + assertThat(this.context.getParent().getBean("foo", String.class)).as("child foo").isEqualTo(QUX + 2); + } + } + } + } + + // ------------------------------------------------------------------------- + + @Configuration + static class ParentConfig { + + @Bean + String foo() { + return FOO; + } + } + + @Configuration + static class Child1Config { + + @Bean + String foo() { + return QUX + 1; + } + + @Bean + String bar() { + return BAZ + 1; + } + } + + @Configuration + static class Child2Config { + + @Bean + String foo() { + return QUX + 2; + } + + @Bean + String bar() { + return BAZ + 2; + } + } + + @Configuration + static class NestedConfig { + + @Bean + String bar() { + return BAR; + } + } + + @Configuration + static class TestInterfaceConfig { + + @Bean + String foo() { + return "test interface"; + } + } + + @ContextConfiguration(classes = TestInterfaceConfig.class) + interface TestInterface { + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/NestedTestsWithSpringAndJUnitJupiterTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/NestedTestsWithSpringAndJUnitJupiterTests.java deleted file mode 100644 index b6a8c554fd..0000000000 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/NestedTestsWithSpringAndJUnitJupiterTests.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2002-2019 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 - * - * https://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.junit.jupiter.nested; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.test.context.junit.SpringJUnitJupiterTestSuite; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; -import org.springframework.test.context.junit.jupiter.nested.NestedTestsWithSpringAndJUnitJupiterTests.TopLevelConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Integration tests that verify support for {@code @Nested} test classes - * in conjunction with the {@link SpringExtension} in a JUnit Jupiter environment. - * - *

To run these tests in an IDE that does not have built-in support for the JUnit - * Platform, simply run {@link SpringJUnitJupiterTestSuite} as a JUnit 4 test. - * - * @author Sam Brannen - * @since 5.0 - * @see NestedTestsWithConstructorInjectionWithSpringAndJUnitJupiterTests - * @see org.springframework.test.context.junit4.nested.NestedTestsWithSpringRulesTests - */ -@SpringJUnitConfig(TopLevelConfig.class) -class NestedTestsWithSpringAndJUnitJupiterTests { - - @Autowired - String foo; - - - @Test - void topLevelTest() { - assertThat(foo).isEqualTo("foo"); - } - - - @Nested - @SpringJUnitConfig(NestedConfig.class) - class NestedTests { - - @Autowired - String bar; - - - @Test - void nestedTest() throws Exception { - // In contrast to nested test classes running in JUnit 4, the foo - // field in the outer instance should have been injected from the - // test ApplicationContext for the outer instance. - assertThat(foo).isEqualTo("foo"); - assertThat(bar).isEqualTo("bar"); - } - } - - // ------------------------------------------------------------------------- - - @Configuration - static class TopLevelConfig { - - @Bean - String foo() { - return "foo"; - } - } - - @Configuration - static class NestedConfig { - - @Bean - String bar() { - return "bar"; - } - } - -} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/NestedTestsWithSqlScriptsAndJUnitJupiterTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/SqlScriptNestedTests.java similarity index 89% rename from spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/NestedTestsWithSqlScriptsAndJUnitJupiterTests.java rename to spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/SqlScriptNestedTests.java index d0174f06e5..bea6c8dc52 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/NestedTestsWithSqlScriptsAndJUnitJupiterTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/SqlScriptNestedTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -45,7 +45,7 @@ import static org.assertj.core.api.Assertions.assertThat; @SpringJUnitConfig(PopulatedSchemaDatabaseConfig.class) @Transactional @TestInstance(Lifecycle.PER_CLASS) -class NestedTestsWithSqlScriptsAndJUnitJupiterTests { +class SqlScriptNestedTests { @Autowired JdbcTemplate jdbcTemplate; @@ -67,6 +67,10 @@ class NestedTestsWithSqlScriptsAndJUnitJupiterTests { } @Nested + // NOTE: the following @SpringJUnitConfig declaration must NOT be removed. + // This was added before the TestContext framework looked up configuration + // on enclosing classes for @Nested test classes. As such, this serves as a + // regression test and cannot be changed. @SpringJUnitConfig(PopulatedSchemaDatabaseConfig.class) @Transactional class NestedTests { diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestConstructorNestedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestConstructorNestedTests.java new file mode 100644 index 0000000000..26aee5af30 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestConstructorNestedTests.java @@ -0,0 +1,169 @@ +/* + * Copyright 2002-2020 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 + * + * https://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.junit.jupiter.nested; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.NestedTestConfiguration; +import org.springframework.test.context.TestConstructor; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.INHERIT; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.OVERRIDE; +import static org.springframework.test.context.TestConstructor.AutowireMode.ALL; +import static org.springframework.test.context.TestConstructor.AutowireMode.ANNOTATED; + +/** + * Integration tests that verify support for {@code @Nested} test classes using + * {@link TestConstructor @TestConstructor} in conjunction with the + * {@link SpringExtension} in a JUnit Jupiter environment. + * + * @author Sam Brannen + * @since 5.3 + */ +@SpringJUnitConfig +@TestConstructor(autowireMode = ALL) +class TestConstructorNestedTests { + + TestConstructorNestedTests(String text) { + assertThat(text).isEqualTo("enigma"); + } + + @Test + void test() { + } + + + @Nested + @SpringJUnitConfig(Config.class) + @TestConstructor(autowireMode = ANNOTATED) + class ConfigOverriddenByDefaultTests { + + @Autowired + ConfigOverriddenByDefaultTests(String text) { + assertThat(text).isEqualTo("enigma"); + } + + @Test + void test() { + } + } + + @Nested + @NestedTestConfiguration(INHERIT) + class InheritedConfigTests { + + InheritedConfigTests(String text) { + assertThat(text).isEqualTo("enigma"); + } + + @Test + void test() { + } + + + @Nested + class DoubleNestedWithImplicitlyInheritedConfigTests { + + DoubleNestedWithImplicitlyInheritedConfigTests(String text) { + assertThat(text).isEqualTo("enigma"); + } + + @Test + void test() { + } + + + @Nested + class TripleNestedWithImplicitlyInheritedConfigTests { + + TripleNestedWithImplicitlyInheritedConfigTests(String text) { + assertThat(text).isEqualTo("enigma"); + } + + @Test + void test() { + } + } + } + + @Nested + @NestedTestConfiguration(OVERRIDE) + @SpringJUnitConfig(Config.class) + @TestConstructor(autowireMode = ANNOTATED) + class DoubleNestedWithOverriddenConfigTests { + + DoubleNestedWithOverriddenConfigTests(@Autowired String text) { + assertThat(text).isEqualTo("enigma"); + } + + @Test + void test() { + } + + + @Nested + @NestedTestConfiguration(INHERIT) + class TripleNestedWithInheritedConfigTests { + + @Autowired + TripleNestedWithInheritedConfigTests(String text) { + assertThat(text).isEqualTo("enigma"); + } + + @Test + void test() { + } + } + + @Nested + @NestedTestConfiguration(INHERIT) + class TripleNestedWithInheritedConfigAndTestInterfaceTests implements TestInterface { + + TripleNestedWithInheritedConfigAndTestInterfaceTests(String text) { + assertThat(text).isEqualTo("enigma"); + } + + @Test + void test() { + } + } + } + } + + // ------------------------------------------------------------------------- + + @Configuration + static class Config { + + @Bean + String text() { + return "enigma"; + } + } + + @TestConstructor(autowireMode = ALL) + interface TestInterface { + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestExecutionListenersNestedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestExecutionListenersNestedTests.java new file mode 100644 index 0000000000..c41d1b036f --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestExecutionListenersNestedTests.java @@ -0,0 +1,192 @@ +/* + * Copyright 2002-2020 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 + * + * https://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.junit.jupiter.nested; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.NestedTestConfiguration; +import org.springframework.test.context.TestContext; +import org.springframework.test.context.TestExecutionListeners; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.springframework.test.context.junit.jupiter.nested.TestExecutionListenersNestedTests.FooTestExecutionListener; +import org.springframework.test.context.support.AbstractTestExecutionListener; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.INHERIT; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.OVERRIDE; + +/** + * Integration tests that verify support for {@code @Nested} test classes using + * {@link TestExecutionListeners @TestExecutionListeners} in conjunction with the + * {@link SpringExtension} in a JUnit Jupiter environment. + * + * @author Sam Brannen + * @since 5.3 + */ +@SpringJUnitConfig +@TestExecutionListeners(FooTestExecutionListener.class) +class TestExecutionListenersNestedTests { + + private static final String FOO = "foo"; + private static final String BAR = "bar"; + private static final String BAZ = "baz"; + private static final String QUX = "qux"; + + private static final List listeners = new ArrayList<>(); + + + @AfterEach + void resetListeners() { + listeners.clear(); + } + + @Test + void test() { + assertThat(listeners).containsExactly(FOO); + } + + + @Nested + @NestedTestConfiguration(INHERIT) + class InheritedConfigTests { + + @Test + void test() { + assertThat(listeners).containsExactly(FOO); + } + } + + @Nested + @SpringJUnitConfig(Config.class) + @TestExecutionListeners(BarTestExecutionListener.class) + class ConfigOverriddenByDefaultTests { + + @Test + void test() { + assertThat(listeners).containsExactly(BAR); + } + } + + @Nested + @NestedTestConfiguration(INHERIT) + @SpringJUnitConfig(Config.class) + @TestExecutionListeners(BarTestExecutionListener.class) + class InheritedAndExtendedConfigTests { + + @Test + void test() { + assertThat(listeners).containsExactly(FOO, BAR); + } + + + @Nested + @NestedTestConfiguration(OVERRIDE) + @SpringJUnitConfig(Config.class) + @TestExecutionListeners(BazTestExecutionListener.class) + class DoubleNestedWithOverriddenConfigTests { + + @Test + void test() { + assertThat(listeners).containsExactly(BAZ); + } + + + @Nested + @NestedTestConfiguration(INHERIT) + @TestExecutionListeners(listeners = BarTestExecutionListener.class, inheritListeners = false) + class TripleNestedWithInheritedConfigButOverriddenListenersTests { + + @Test + void test() { + assertThat(listeners).containsExactly(BAR); + } + } + + @Nested + @NestedTestConfiguration(INHERIT) + class TripleNestedWithInheritedConfigAndTestInterfaceTests implements TestInterface { + + @Test + void test() { + assertThat(listeners).containsExactly(BAZ, QUX); + } + } + } + + } + + // ------------------------------------------------------------------------- + + @Configuration + static class Config { + /* no user beans required for these tests */ + } + + private static abstract class BaseTestExecutionListener extends AbstractTestExecutionListener { + + protected abstract String name(); + + @Override + public final void beforeTestClass(TestContext testContext) { + listeners.add(name()); + } + } + + static class FooTestExecutionListener extends BaseTestExecutionListener { + + @Override + protected String name() { + return FOO; + } + } + + static class BarTestExecutionListener extends BaseTestExecutionListener { + + @Override + protected String name() { + return BAR; + } + } + + static class BazTestExecutionListener extends BaseTestExecutionListener { + + @Override + protected String name() { + return BAZ; + } + } + + static class QuxTestExecutionListener extends BaseTestExecutionListener { + + @Override + protected String name() { + return QUX; + } + } + + @TestExecutionListeners(QuxTestExecutionListener.class) + interface TestInterface { + } + +} 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 new file mode 100644 index 0000000000..2423b63f3c --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestPropertySourceNestedTests.java @@ -0,0 +1,195 @@ +/* + * Copyright 2002-2020 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 + * + * https://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.junit.jupiter.nested; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.test.context.NestedTestConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.springframework.test.context.junit.jupiter.nested.TestPropertySourceNestedTests.Config; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.INHERIT; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.OVERRIDE; + +/** + * Integration tests that verify support for {@code @Nested} test classes using + * {@link TestPropertySource @TestPropertySource} in conjunction with the + * {@link SpringExtension} in a JUnit Jupiter environment. + * + * @author Sam Brannen + * @since 5.3 + */ +@SpringJUnitConfig(Config.class) +@TestPropertySource(properties = "p1 = v1") +class TestPropertySourceNestedTests { + + @Autowired + Environment env1; + + + @Test + void propertiesInEnvironment() { + assertThat(env1.getProperty("p1")).isEqualTo("v1"); + } + + + @Nested + @NestedTestConfiguration(INHERIT) + class InheritedConfigTests { + + @Autowired + Environment env2; + + + @Test + void propertiesInEnvironment() { + assertThat(env1.getProperty("p1")).isEqualTo("v1"); + assertThat(env2.getProperty("p1")).isEqualTo("v1"); + assertThat(env1).isSameAs(env2); + } + } + + @Nested + @SpringJUnitConfig(Config.class) + @TestPropertySource(properties = "p2 = v2") + class ConfigOverriddenByDefaultTests { + + @Autowired + Environment env2; + + + @Test + void propertiesInEnvironment() { + assertThat(env1.getProperty("p1")).isEqualTo("v1"); + assertThat(env1).isNotSameAs(env2); + assertThat(env2.getProperty("p1")).isNull(); + assertThat(env2.getProperty("p2")).isEqualTo("v2"); + } + } + + @Nested + @NestedTestConfiguration(INHERIT) + @TestPropertySource(properties = "p2a = v2a") + @TestPropertySource(properties = "p2b = v2b") + class InheritedAndExtendedConfigTests { + + @Autowired + Environment env2; + + + @Test + void propertiesInEnvironment() { + assertThat(env1.getProperty("p1")).isEqualTo("v1"); + assertThat(env1).isNotSameAs(env2); + assertThat(env2.getProperty("p1")).isEqualTo("v1"); + assertThat(env2.getProperty("p2a")).isEqualTo("v2a"); + assertThat(env2.getProperty("p2b")).isEqualTo("v2b"); + } + + + @Nested + @NestedTestConfiguration(OVERRIDE) + @SpringJUnitConfig(Config.class) + @TestPropertySource(properties = "p3 = v3") + class L3WithOverriddenConfigTests { + + @Autowired + Environment env3; + + + @Test + void propertiesInEnvironment() { + assertThat(env1.getProperty("p1")).isEqualTo("v1"); + assertThat(env1).isNotSameAs(env2); + assertThat(env2.getProperty("p1")).isEqualTo("v1"); + assertThat(env2.getProperty("p2a")).isEqualTo("v2a"); + assertThat(env2.getProperty("p2b")).isEqualTo("v2b"); + assertThat(env2).isNotSameAs(env3); + assertThat(env3.getProperty("p1")).isNull(); + assertThat(env3.getProperty("p2")).isNull(); + assertThat(env3.getProperty("p3")).isEqualTo("v3"); + } + + + @Nested + @NestedTestConfiguration(INHERIT) + @TestPropertySource(properties = {"p3 = v34", "p4 = v4"}, inheritProperties = false) + class L4WithInheritedConfigButOverriddenTestPropertiesTests { + + @Autowired + Environment env4; + + + @Test + void propertiesInEnvironment() { + assertThat(env1.getProperty("p1")).isEqualTo("v1"); + assertThat(env1).isNotSameAs(env2); + assertThat(env2.getProperty("p1")).isEqualTo("v1"); + assertThat(env2.getProperty("p2a")).isEqualTo("v2a"); + assertThat(env2.getProperty("p2b")).isEqualTo("v2b"); + assertThat(env2).isNotSameAs(env3); + assertThat(env3.getProperty("p1")).isNull(); + assertThat(env3.getProperty("p2")).isNull(); + assertThat(env3.getProperty("p3")).isEqualTo("v3"); + assertThat(env3).isNotSameAs(env4); + assertThat(env4.getProperty("p1")).isNull(); + assertThat(env4.getProperty("p2")).isNull(); + assertThat(env4.getProperty("p3")).isEqualTo("v34"); + assertThat(env4.getProperty("p4")).isEqualTo("v4"); + } + + @Nested + class L5WithInheritedConfigAndTestInterfaceTests implements TestInterface { + + @Autowired + Environment env5; + + + @Test + void propertiesInEnvironment() { + assertThat(env4).isNotSameAs(env5); + assertThat(env5.getProperty("foo")).isEqualTo("bar"); + assertThat(env5.getProperty("enigma")).isEqualTo("42"); + assertThat(env5.getProperty("p1")).isNull(); + assertThat(env5.getProperty("p2")).isNull(); + assertThat(env5.getProperty("p3")).isEqualTo("v34"); + assertThat(env5.getProperty("p4")).isEqualTo("v4"); + } + } + } + } + } + + // ------------------------------------------------------------------------- + + @Configuration + static class Config { + /* no user beans required for these tests */ + } + + @TestPropertySource(properties = { "foo = bar", "enigma: 42" }) + interface TestInterface { + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TransactionalNestedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TransactionalNestedTests.java new file mode 100644 index 0000000000..281c4cdcd8 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TransactionalNestedTests.java @@ -0,0 +1,185 @@ +/* + * Copyright 2002-2020 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 + * + * https://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.junit.jupiter.nested; + +import javax.sql.DataSource; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.test.annotation.Commit; +import org.springframework.test.annotation.Rollback; +import org.springframework.test.context.NestedTestConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.springframework.test.context.transaction.TestTransaction; +import org.springframework.transaction.TransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.transaction.annotation.Transactional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.INHERIT; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.OVERRIDE; +import static org.springframework.test.transaction.TransactionAssert.assertThatTransaction; +import static org.springframework.transaction.annotation.Propagation.NOT_SUPPORTED; + +/** + * Integration tests that verify support for {@code @Nested} test classes using + * {@link Transactional @Transactional} in conjunction with the + * {@link SpringExtension} in a JUnit Jupiter environment. + * + * @author Sam Brannen + * @since 5.3 + */ +@SpringJUnitConfig +@Transactional +@Commit +class TransactionalNestedTests { + + @Test + void transactional(@Autowired DataSource dataSource) { + assertThatTransaction().isActive(); + assertThat(dataSource).isNotNull(); + assertCommit(); + } + + + @Nested + @SpringJUnitConfig(Config.class) + class ConfigOverriddenByDefaultTests { + + @Test + void notTransactional(@Autowired DataSource dataSource) { + assertThatTransaction().isNotActive(); + assertThat(dataSource).isNotNull(); + } + } + + @Nested + @NestedTestConfiguration(INHERIT) + class InheritedConfigTests { + + @Test + void transactional(@Autowired DataSource dataSource) { + assertThatTransaction().isActive(); + assertThat(dataSource).isNotNull(); + assertCommit(); + } + + + @Nested + class DoubleNestedWithImplicitlyInheritedConfigTests { + + @Test + void transactional(@Autowired DataSource dataSource) { + assertThatTransaction().isActive(); + assertThat(dataSource).isNotNull(); + assertCommit(); + } + + + @Nested + @Rollback + class TripleNestedWithImplicitlyInheritedConfigTests { + + @Test + void transactional(@Autowired DataSource dataSource) { + assertThatTransaction().isActive(); + assertThat(dataSource).isNotNull(); + assertRollback(); + } + } + } + + @Nested + @NestedTestConfiguration(OVERRIDE) + @SpringJUnitConfig(Config.class) + @Transactional + @Rollback + class DoubleNestedWithOverriddenConfigTests { + + @Test + void transactional(@Autowired DataSource dataSource) { + assertThatTransaction().isActive(); + assertThat(dataSource).isNotNull(); + assertRollback(); + } + + + @Nested + @NestedTestConfiguration(INHERIT) + @Commit + class TripleNestedWithInheritedConfigTests { + + @Test + void transactional(@Autowired DataSource dataSource) { + assertThatTransaction().isActive(); + assertThat(dataSource).isNotNull(); + assertCommit(); + } + } + + @Nested + @NestedTestConfiguration(INHERIT) + class TripleNestedWithInheritedConfigAndTestInterfaceTests implements TestInterface { + + @Test + void notTransactional(@Autowired DataSource dataSource) { + assertThatTransaction().isNotActive(); + assertThat(dataSource).isNotNull(); + } + } + } + } + + + private void assertCommit() { + assertThat(TestTransaction.isFlaggedForRollback()).as("flagged for commit").isFalse(); + } + + private void assertRollback() { + assertThat(TestTransaction.isFlaggedForRollback()).as("flagged for rollback").isTrue(); + } + + // ------------------------------------------------------------------------- + + + @Configuration + @EnableTransactionManagement + static class Config { + + @Bean + TransactionManager transactionManager(DataSource dataSource) { + return new DataSourceTransactionManager(dataSource); + } + + @Bean + DataSource dataSource() { + return new EmbeddedDatabaseBuilder().generateUniqueName(true).build(); + } + } + + @Transactional(propagation = NOT_SUPPORTED) + interface TestInterface { + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/WebAppConfigurationNestedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/WebAppConfigurationNestedTests.java new file mode 100644 index 0000000000..d62de1b2af --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/WebAppConfigurationNestedTests.java @@ -0,0 +1,137 @@ +/* + * Copyright 2002-2020 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 + * + * https://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.junit.jupiter.nested; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.NestedTestConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.springframework.test.context.junit.jupiter.nested.WebAppConfigurationNestedTests.Config; +import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.web.context.WebApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.INHERIT; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.OVERRIDE; + +/** + * Integration tests that verify support for {@code @Nested} test classes using + * {@link WebAppConfiguration @WebAppConfiguration} in conjunction with the + * {@link SpringExtension} in a JUnit Jupiter environment. + * + * @author Sam Brannen + * @since 5.0 + * @see ConstructorInjectionNestedTests + * @see org.springframework.test.context.junit4.nested.NestedTestsWithSpringRulesTests + */ +@SpringJUnitWebConfig(Config.class) +class WebAppConfigurationNestedTests { + + @Test + void test(ApplicationContext context) { + assertThat(context).isInstanceOf(WebApplicationContext.class); + } + + + @Nested + @SpringJUnitConfig(Config.class) + class ConfigOverriddenByDefaultTests { + + @Test + void test(ApplicationContext context) { + assertThat(context).isNotInstanceOf(WebApplicationContext.class); + } + } + + @Nested + @SpringJUnitWebConfig(Config.class) + class ConfigOverriddenByDefaultWebTests { + + @Test + void test(ApplicationContext context) { + assertThat(context).isInstanceOf(WebApplicationContext.class); + } + } + + @Nested + @NestedTestConfiguration(INHERIT) + class NestedWithInheritedConfigTests { + + @Test + void test(ApplicationContext context) { + assertThat(context).isInstanceOf(WebApplicationContext.class); + } + + + @Nested + class DoubleNestedWithImplicitlyInheritedConfigWebTests { + + @Test + void test(ApplicationContext context) { + assertThat(context).isInstanceOf(WebApplicationContext.class); + } + } + + @Nested + @NestedTestConfiguration(OVERRIDE) + @SpringJUnitConfig(Config.class) + class DoubleNestedWithOverriddenConfigWebTests { + + @Test + void test(ApplicationContext context) { + assertThat(context).isNotInstanceOf(WebApplicationContext.class); + } + + + @Nested + @NestedTestConfiguration(INHERIT) + class TripleNestedWithInheritedConfigWebTests { + + @Test + void test(ApplicationContext context) { + assertThat(context).isNotInstanceOf(WebApplicationContext.class); + } + } + + @Nested + @NestedTestConfiguration(INHERIT) + class TripleNestedWithInheritedConfigAndTestInterfaceTests implements TestInterface { + + @Test + void test(ApplicationContext context) { + assertThat(context).isInstanceOf(WebApplicationContext.class); + } + } + } + } + + // ------------------------------------------------------------------------- + + @Configuration + static class Config { + } + + @WebAppConfiguration + interface TestInterface { + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/support/AbstractContextConfigurationUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/support/AbstractContextConfigurationUtilsTests.java index f4c843f90e..d7a35fe437 100644 --- a/spring-test/src/test/java/org/springframework/test/context/support/AbstractContextConfigurationUtilsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/AbstractContextConfigurationUtilsTests.java @@ -33,12 +33,16 @@ import org.springframework.test.context.BootstrapTestUtils; import org.springframework.test.context.CacheAwareContextLoaderDelegate; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfigurationAttributes; +import org.springframework.test.context.ContextHierarchy; import org.springframework.test.context.ContextLoader; import org.springframework.test.context.MergedContextConfiguration; +import org.springframework.test.context.NestedTestConfiguration; import org.springframework.test.context.TestContextBootstrapper; import org.springframework.test.context.web.WebAppConfiguration; -import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.INHERIT; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.OVERRIDE; /** * Abstract base class for tests involving {@link ContextLoaderUtils}, @@ -68,11 +72,13 @@ abstract class AbstractContextConfigurationUtilsTests { String[] expectedLocations, Class[] expectedClasses, Class expectedContextLoaderClass, boolean expectedInheritLocations) { - assertThat(attributes.getDeclaringClass()).as("declaring class").isEqualTo(expectedDeclaringClass); - assertThat(attributes.getLocations()).as("locations").isEqualTo(expectedLocations); - assertThat(attributes.getClasses()).as("classes").isEqualTo(expectedClasses); - assertThat(attributes.isInheritLocations()).as("inherit locations").isEqualTo(expectedInheritLocations); - assertThat(attributes.getContextLoaderClass()).as("context loader").isEqualTo(expectedContextLoaderClass); + assertSoftly(softly -> { + softly.assertThat(attributes.getDeclaringClass()).as("declaring class").isEqualTo(expectedDeclaringClass); + softly.assertThat(attributes.getLocations()).as("locations").isEqualTo(expectedLocations); + softly.assertThat(attributes.getClasses()).as("classes").isEqualTo(expectedClasses); + softly.assertThat(attributes.isInheritLocations()).as("inherit locations").isEqualTo(expectedInheritLocations); + softly.assertThat(attributes.getContextLoaderClass()).as("context loader").isEqualTo(expectedContextLoaderClass); + }); } void assertMergedConfig(MergedContextConfiguration mergedConfig, Class expectedTestClass, @@ -91,21 +97,22 @@ abstract class AbstractContextConfigurationUtilsTests { Set>> expectedInitializerClasses, Class expectedContextLoaderClass) { - assertThat(mergedConfig).isNotNull(); - assertThat(mergedConfig.getTestClass()).isEqualTo(expectedTestClass); - assertThat(mergedConfig.getLocations()).isNotNull(); - assertThat(mergedConfig.getLocations()).isEqualTo(expectedLocations); - assertThat(mergedConfig.getClasses()).isNotNull(); - assertThat(mergedConfig.getClasses()).isEqualTo(expectedClasses); - assertThat(mergedConfig.getActiveProfiles()).isNotNull(); - if (expectedContextLoaderClass == null) { - assertThat(mergedConfig.getContextLoader()).isNull(); - } - else { - assertThat(mergedConfig.getContextLoader().getClass()).isEqualTo(expectedContextLoaderClass); - } - assertThat(mergedConfig.getContextInitializerClasses()).isNotNull(); - assertThat(mergedConfig.getContextInitializerClasses()).isEqualTo(expectedInitializerClasses); + assertSoftly(softly -> { + softly.assertThat(mergedConfig).as("merged config").isNotNull(); + softly.assertThat(mergedConfig.getTestClass()).as("test class").isEqualTo(expectedTestClass); + softly.assertThat(mergedConfig.getLocations()).as("locations").containsExactly(expectedLocations); + softly.assertThat(mergedConfig.getClasses()).as("classes").containsExactly(expectedClasses); + softly.assertThat(mergedConfig.getActiveProfiles()).as("active profiles").isNotNull(); + + if (expectedContextLoaderClass == null) { + softly.assertThat(mergedConfig.getContextLoader()).as("context loader").isNull(); + } + else { + softly.assertThat(mergedConfig.getContextLoader().getClass()).as("context loader").isEqualTo(expectedContextLoaderClass); + } + softly.assertThat(mergedConfig.getContextInitializerClasses()).as("context initializers").isNotNull(); + softly.assertThat(mergedConfig.getContextInitializerClasses()).as("context initializers").isEqualTo(expectedInitializerClasses); + }); } @SafeVarargs @@ -130,6 +137,14 @@ abstract class AbstractContextConfigurationUtilsTests { static class BarConfig { } + @Configuration + static class BazConfig { + } + + @Configuration + static class QuuxConfig { + } + @ContextConfiguration("/foo.xml") @ActiveProfiles(profiles = "foo") @Retention(RetentionPolicy.RUNTIME) @@ -219,4 +234,46 @@ abstract class AbstractContextConfigurationUtilsTests { static class PropertiesClassesFoo { } + @ContextConfiguration(classes = FooConfig.class, loader = AnnotationConfigContextLoader.class) + @NestedTestConfiguration(INHERIT) + static class OuterTestCase { + + class NestedTestCaseWithInheritedConfig { + } + + @ContextConfiguration(classes = BarConfig.class) + class NestedTestCaseWithMergedInheritedConfig { + } + + @NestedTestConfiguration(OVERRIDE) + @ContextConfiguration(classes = BarConfig.class) + class NestedTestCaseWithOverriddenConfig { + + @NestedTestConfiguration(INHERIT) + class DoubleNestedTestCaseWithInheritedOverriddenConfig { + } + } + + } + + @ContextHierarchy({ // + @ContextConfiguration(classes = FooConfig.class, loader = AnnotationConfigContextLoader.class, name = "foo"), // + @ContextConfiguration(classes = BarConfig.class, loader = AnnotationConfigContextLoader.class, name = "bar")// + }) + @NestedTestConfiguration(INHERIT) + static class ContextHierarchyOuterTestCase { + + class NestedTestCaseWithInheritedConfig { + } + + @ContextConfiguration(classes = BazConfig.class, loader = AnnotationConfigContextLoader.class, name = "bar") + class NestedTestCaseWithMergedInheritedConfig { + } + + @ContextConfiguration(classes = QuuxConfig.class, loader = AnnotationConfigContextLoader.class, name = "foo", inheritLocations = false) + class NestedTestCaseWithOverriddenConfig { + } + + } + } diff --git a/spring-test/src/test/java/org/springframework/test/context/support/BootstrapTestUtilsMergedConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/support/BootstrapTestUtilsMergedConfigTests.java index c235e7f2ca..8462555acb 100644 --- a/spring-test/src/test/java/org/springframework/test/context/support/BootstrapTestUtilsMergedConfigTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/BootstrapTestUtilsMergedConfigTests.java @@ -21,6 +21,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.assertj.core.api.AssertionsForClassTypes; import org.junit.jupiter.api.Test; import org.springframework.test.context.BootstrapTestUtils; @@ -220,6 +221,137 @@ class BootstrapTestUtilsMergedConfigTests extends AbstractContextConfigurationUt assertThat(mergedConfig.getPropertySourceProperties()).isEmpty(); } + /** + * @since 5.3 + */ + @Test + public void buildMergedConfigForNestedTestClassWithInheritedConfig() { + Class testClass = OuterTestCase.NestedTestCaseWithInheritedConfig.class; + Class[] expectedClasses = array(FooConfig.class); + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass); + + assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, + AnnotationConfigContextLoader.class); + } + + /** + * @since 5.3 + */ + @Test + public void buildMergedConfigForNestedTestClassWithMergedInheritedConfig() { + Class testClass = OuterTestCase.NestedTestCaseWithMergedInheritedConfig.class; + Class[] expectedClasses = array(FooConfig.class, BarConfig.class); + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass); + + assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, + AnnotationConfigContextLoader.class); + } + + /** + * @since 5.3 + */ + @Test + public void buildMergedConfigForNestedTestClassWithOverriddenConfig() { + Class testClass = OuterTestCase.NestedTestCaseWithOverriddenConfig.class; + Class[] expectedClasses = array(BarConfig.class); + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass); + + assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, + DelegatingSmartContextLoader.class); + } + + /** + * @since 5.3 + */ + @Test + public void buildMergedConfigForDoubleNestedTestClassWithInheritedOverriddenConfig() { + Class testClass = OuterTestCase.NestedTestCaseWithOverriddenConfig.DoubleNestedTestCaseWithInheritedOverriddenConfig.class; + Class[] expectedClasses = array(BarConfig.class); + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass); + + assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, + DelegatingSmartContextLoader.class); + } + + /** + * @since 5.3 + */ + @Test + public void buildMergedConfigForContextHierarchy() { + Class testClass = ContextHierarchyOuterTestCase.class; + Class[] expectedClasses = array(BarConfig.class); + + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass); + assertThat(mergedConfig).as("merged config").isNotNull(); + + MergedContextConfiguration parent = mergedConfig.getParent(); + assertThat(parent).as("parent config").isNotNull(); + // The following does not work -- at least not in Eclipse. + // asssertThat(parent.getClasses())... + // So we use AssertionsForClassTypes directly. + AssertionsForClassTypes.assertThat(parent.getClasses()).containsExactly(FooConfig.class); + + assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, + AnnotationConfigContextLoader.class); + } + + /** + * @since 5.3 + */ + @Test + public void buildMergedConfigForNestedTestClassWithInheritedConfigForContextHierarchy() { + Class enclosingTestClass = ContextHierarchyOuterTestCase.class; + Class testClass = ContextHierarchyOuterTestCase.NestedTestCaseWithInheritedConfig.class; + Class[] expectedClasses = array(BarConfig.class); + + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass); + assertThat(mergedConfig).as("merged config").isNotNull(); + + MergedContextConfiguration parent = mergedConfig.getParent(); + assertThat(parent).as("parent config").isNotNull(); + AssertionsForClassTypes.assertThat(parent.getClasses()).containsExactly(FooConfig.class); + + assertMergedConfig(mergedConfig, enclosingTestClass, EMPTY_STRING_ARRAY, expectedClasses, + AnnotationConfigContextLoader.class); + } + + /** + * @since 5.3 + */ + @Test + public void buildMergedConfigForNestedTestClassWithMergedInheritedConfigForContextHierarchy() { + Class testClass = ContextHierarchyOuterTestCase.NestedTestCaseWithMergedInheritedConfig.class; + Class[] expectedClasses = array(BarConfig.class, BazConfig.class); + + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass); + assertThat(mergedConfig).as("merged config").isNotNull(); + + MergedContextConfiguration parent = mergedConfig.getParent(); + assertThat(parent).as("parent config").isNotNull(); + AssertionsForClassTypes.assertThat(parent.getClasses()).containsExactly(FooConfig.class); + + assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, + AnnotationConfigContextLoader.class); + } + + /** + * @since 5.3 + */ + @Test + public void buildMergedConfigForNestedTestClassWithOverriddenConfigForContextHierarchy() { + Class testClass = ContextHierarchyOuterTestCase.NestedTestCaseWithOverriddenConfig.class; + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass); + assertThat(mergedConfig).as("merged config").isNotNull(); + + MergedContextConfiguration parent = mergedConfig.getParent(); + assertThat(parent).as("parent config").isNotNull(); + + assertMergedConfig(parent, testClass, EMPTY_STRING_ARRAY, array(QuuxConfig.class), + AnnotationConfigContextLoader.class); + assertMergedConfig(mergedConfig, ContextHierarchyOuterTestCase.class, EMPTY_STRING_ARRAY, + array(BarConfig.class), AnnotationConfigContextLoader.class); + } + @ContextConfiguration @Retention(RetentionPolicy.RUNTIME) diff --git a/spring-test/src/test/java/org/springframework/test/context/support/DirtiesContextTestExecutionListenerTests.java b/spring-test/src/test/java/org/springframework/test/context/support/DirtiesContextTestExecutionListenerTests.java index 1096f4758c..f79dfd46a1 100644 --- a/spring-test/src/test/java/org/springframework/test/context/support/DirtiesContextTestExecutionListenerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/DirtiesContextTestExecutionListenerTests.java @@ -19,12 +19,15 @@ package org.springframework.test.context.support; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.mockito.BDDMockito; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.annotation.DirtiesContext.HierarchyMode; +import org.springframework.test.context.NestedTestConfiguration; import org.springframework.test.context.TestContext; import org.springframework.test.context.TestExecutionListener; @@ -40,6 +43,8 @@ import static org.springframework.test.annotation.DirtiesContext.ClassMode.BEFOR import static org.springframework.test.annotation.DirtiesContext.HierarchyMode.CURRENT_LEVEL; import static org.springframework.test.annotation.DirtiesContext.HierarchyMode.EXHAUSTIVE; import static org.springframework.test.annotation.DirtiesContext.MethodMode.BEFORE_METHOD; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.INHERIT; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.OVERRIDE; /** * Unit tests for {@link DirtiesContextBeforeModesTestExecutionListener}. @@ -48,6 +53,7 @@ import static org.springframework.test.annotation.DirtiesContext.MethodMode.BEFO * @author Sam Brannen * @since 4.0 */ +@DisplayName("@DirtiesContext TestExecutionListener tests") class DirtiesContextTestExecutionListenerTests { private final TestExecutionListener beforeListener = new DirtiesContextBeforeModesTestExecutionListener(); @@ -55,12 +61,261 @@ class DirtiesContextTestExecutionListenerTests { private final TestContext testContext = mock(TestContext.class); - @Test - void beforeAndAfterTestMethodForDirtiesContextDeclaredLocallyOnMethodWithBeforeMethodMode() throws Exception { - Class clazz = getClass(); + @Nested + @DisplayName("Before and after test method") + class BeforeAndAfterTestMethodTests { + + @Test + void declaredLocallyOnMethodWithBeforeMethodMode() throws Exception { + Class clazz = getClass().getEnclosingClass(); + BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); + given(testContext.getTestMethod()).willReturn( + clazz.getDeclaredMethod("dirtiesContextDeclaredLocallyWithBeforeMethodMode")); + beforeListener.beforeTestMethod(testContext); + afterListener.beforeTestMethod(testContext); + verify(testContext, times(1)).markApplicationContextDirty(EXHAUSTIVE); + afterListener.afterTestMethod(testContext); + beforeListener.afterTestMethod(testContext); + verify(testContext, times(1)).markApplicationContextDirty(EXHAUSTIVE); + } + + @Test + void declaredLocallyOnMethodWithAfterMethodMode() throws Exception { + Class clazz = getClass().getEnclosingClass(); + BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); + given(testContext.getTestMethod()).willReturn( + clazz.getDeclaredMethod("dirtiesContextDeclaredLocallyWithAfterMethodMode")); + beforeListener.beforeTestMethod(testContext); + afterListener.beforeTestMethod(testContext); + verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); + afterListener.afterTestMethod(testContext); + beforeListener.afterTestMethod(testContext); + verify(testContext, times(1)).markApplicationContextDirty(EXHAUSTIVE); + } + + @Test + void declaredOnMethodViaMetaAnnotationWithAfterMethodMode() throws Exception { + Class clazz = getClass().getEnclosingClass(); + BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); + given(testContext.getTestMethod()).willReturn( + clazz.getDeclaredMethod("dirtiesContextDeclaredViaMetaAnnotationWithAfterMethodMode")); + beforeListener.beforeTestMethod(testContext); + afterListener.beforeTestMethod(testContext); + verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); + afterListener.afterTestMethod(testContext); + beforeListener.afterTestMethod(testContext); + verify(testContext, times(1)).markApplicationContextDirty(EXHAUSTIVE); + } + + @Test + void declaredLocallyOnClassBeforeEachTestMethod() throws Exception { + assertBeforeMethod(DirtiesContextDeclaredLocallyBeforeEachTestMethod.class); + } + + @Test + void declaredLocallyOnClassAfterEachTestMethod() throws Exception { + assertAfterMethod(DirtiesContextDeclaredLocallyAfterEachTestMethod.class); + } + + @Test + void declaredViaMetaAnnotationOnClassAfterEachTestMethod() throws Exception { + assertAfterMethod(DirtiesContextDeclaredViaMetaAnnotationAfterEachTestMethod.class); + } + + @Test + void declaredLocallyOnClassBeforeClass() throws Exception { + Class clazz = DirtiesContextDeclaredLocallyBeforeClass.class; + BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); + given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("test")); + beforeListener.beforeTestMethod(testContext); + afterListener.beforeTestMethod(testContext); + afterListener.afterTestMethod(testContext); + beforeListener.afterTestMethod(testContext); + verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); + } + + @Test + void declaredLocallyOnClassAfterClass() throws Exception { + Class clazz = DirtiesContextDeclaredLocallyAfterClass.class; + BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); + given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("test")); + beforeListener.beforeTestMethod(testContext); + afterListener.beforeTestMethod(testContext); + afterListener.afterTestMethod(testContext); + beforeListener.afterTestMethod(testContext); + verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); + } + + @Test + void declaredViaMetaAnnotationOnClassAfterClass() throws Exception { + Class clazz = DirtiesContextDeclaredViaMetaAnnotationAfterClass.class; + BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); + given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("test")); + beforeListener.beforeTestMethod(testContext); + afterListener.beforeTestMethod(testContext); + afterListener.afterTestMethod(testContext); + beforeListener.afterTestMethod(testContext); + verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); + } + + @Test + void beforeAndAfterTestMethodForDirtiesContextViaMetaAnnotationWithOverrides() throws Exception { + Class clazz = DirtiesContextViaMetaAnnotationWithOverrides.class; + BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); + given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("test")); + beforeListener.beforeTestMethod(testContext); + afterListener.beforeTestMethod(testContext); + verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); + afterListener.afterTestMethod(testContext); + beforeListener.afterTestMethod(testContext); + verify(testContext, times(1)).markApplicationContextDirty(CURRENT_LEVEL); + } + } + + @Nested + @DisplayName("Before and after test class") + class BeforeAndAfterTestClassTests { + + @Test + void declaredLocallyOnMethod() throws Exception { + Class clazz = getClass().getEnclosingClass(); + BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); + beforeListener.beforeTestClass(testContext); + afterListener.beforeTestClass(testContext); + afterListener.afterTestClass(testContext); + beforeListener.afterTestClass(testContext); + verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); + } + + @Test + void declaredLocallyOnClassBeforeEachTestMethod() throws Exception { + Class clazz = DirtiesContextDeclaredLocallyBeforeEachTestMethod.class; + BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); + beforeListener.beforeTestClass(testContext); + afterListener.beforeTestClass(testContext); + afterListener.afterTestClass(testContext); + beforeListener.afterTestClass(testContext); + verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); + } + + @Test + void declaredLocallyOnClassAfterEachTestMethod() throws Exception { + Class clazz = DirtiesContextDeclaredLocallyAfterEachTestMethod.class; + BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); + beforeListener.beforeTestClass(testContext); + afterListener.beforeTestClass(testContext); + afterListener.afterTestClass(testContext); + beforeListener.afterTestClass(testContext); + verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); + } + + @Test + void declaredViaMetaAnnotationOnClassAfterEachTestMethod() throws Exception { + Class clazz = DirtiesContextDeclaredViaMetaAnnotationAfterEachTestMethod.class; + BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); + beforeListener.beforeTestClass(testContext); + afterListener.beforeTestClass(testContext); + afterListener.afterTestClass(testContext); + beforeListener.afterTestClass(testContext); + verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); + } + + @Test + void declaredLocallyOnClassBeforeClass() throws Exception { + assertBeforeClass(DirtiesContextDeclaredLocallyBeforeClass.class); + } + + @Test + void declaredLocallyOnClassAfterClass() throws Exception { + assertAfterClass(DirtiesContextDeclaredLocallyAfterClass.class); + } + + @Test + void declaredViaMetaAnnotationOnClassAfterClass() throws Exception { + assertAfterClass(DirtiesContextDeclaredViaMetaAnnotationAfterClass.class); + } + + @Test + void declaredViaMetaAnnotationWithOverrides() throws Exception { + Class clazz = DirtiesContextViaMetaAnnotationWithOverrides.class; + BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); + beforeListener.beforeTestClass(testContext); + afterListener.beforeTestClass(testContext); + afterListener.afterTestClass(testContext); + beforeListener.afterTestClass(testContext); + verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); + } + + @Test + void declaredViaMetaAnnotationWithOverriddenAttributes() throws Exception { + assertAfterClass(DirtiesContextViaMetaAnnotationWithOverridenAttributes.class); + } + } + + @Nested + @DisplayName("Nested config - before and after test method modes") + class NestedBeforeAndAfterTestMethodTests { + + @Test + void onTopLevelClassWithBeforeEachTestMethod() throws Exception { + assertBeforeMethod(BeforeAndAfterTestMethodTopLevelClass.class); + } + + @Test + void onNestedClassWithConfigOverriddenByDefaultWithAfterClass() throws Exception { + assertAfterMethod(BeforeAndAfterTestMethodTopLevelClass.ConfigOverriddenByDefault.class); + } + + @Test + void onNestedClassWithInheritedConfigWithBeforeEachTestMethod() throws Exception { + assertBeforeMethod(BeforeAndAfterTestMethodTopLevelClass.InheritedConfig.class); + } + + @Test + void onNestedClassWithOverriddenConfigWithAfterClass() throws Exception { + assertAfterMethod(BeforeAndAfterTestMethodTopLevelClass.OverriddenConfig.class); + } + + @Test + void onNestedClassWithInheritedConfigButOverriddenWithBeforeEachTestMethod() throws Exception { + assertBeforeMethod(BeforeAndAfterTestMethodTopLevelClass.OverriddenConfig.InheritedConfigButOverridden.class); + } + } + + @Nested + @DisplayName("Nested config - before and after test class modes") + class NestedBeforeAndAfterTestClassTests { + + @Test + void onTopLevelClassWithBeforeClass() throws Exception { + assertBeforeClass(BeforeAndAfterTestClassTopLevelClass.class); + } + + @Test + void onNestedClassWithConfigOverriddenByDefaultWithAfterClass() throws Exception { + assertAfterClass(BeforeAndAfterTestClassTopLevelClass.ConfigOverriddenByDefault.class); + } + + @Test + void onNestedClassWithInheritedConfigWithBeforeClass() throws Exception { + assertBeforeClass(BeforeAndAfterTestClassTopLevelClass.InheritedConfig.class); + } + + @Test + void onNestedClassWithOverriddenConfigWithAfterClass() throws Exception { + assertAfterClass(BeforeAndAfterTestClassTopLevelClass.OverriddenConfig.class); + } + + @Test + void onNestedClassWithInheritedConfigButOverriddenWithBeforeClass() throws Exception { + assertBeforeClass(BeforeAndAfterTestClassTopLevelClass.OverriddenConfig.InheritedConfigButOverridden.class); + } + } + + + private void assertBeforeMethod(Class clazz) throws Exception { BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); - given(testContext.getTestMethod()).willReturn( - clazz.getDeclaredMethod("dirtiesContextDeclaredLocallyWithBeforeMethodMode")); + given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("test")); beforeListener.beforeTestMethod(testContext); afterListener.beforeTestMethod(testContext); verify(testContext, times(1)).markApplicationContextDirty(EXHAUSTIVE); @@ -69,12 +324,9 @@ class DirtiesContextTestExecutionListenerTests { verify(testContext, times(1)).markApplicationContextDirty(EXHAUSTIVE); } - @Test - void beforeAndAfterTestMethodForDirtiesContextDeclaredLocallyOnMethodWithAfterMethodMode() throws Exception { - Class clazz = getClass(); + private void assertAfterMethod(Class clazz) throws NoSuchMethodException, Exception { BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); - given(testContext.getTestMethod()).willReturn( - clazz.getDeclaredMethod("dirtiesContextDeclaredLocallyWithAfterMethodMode")); + given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("test")); beforeListener.beforeTestMethod(testContext); afterListener.beforeTestMethod(testContext); verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); @@ -83,160 +335,7 @@ class DirtiesContextTestExecutionListenerTests { verify(testContext, times(1)).markApplicationContextDirty(EXHAUSTIVE); } - @Test - void beforeAndAfterTestMethodForDirtiesContextDeclaredOnMethodViaMetaAnnotationWithAfterMethodMode() - throws Exception { - Class clazz = getClass(); - BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); - given(testContext.getTestMethod()).willReturn( - clazz.getDeclaredMethod("dirtiesContextDeclaredViaMetaAnnotationWithAfterMethodMode")); - beforeListener.beforeTestMethod(testContext); - afterListener.beforeTestMethod(testContext); - verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); - afterListener.afterTestMethod(testContext); - beforeListener.afterTestMethod(testContext); - verify(testContext, times(1)).markApplicationContextDirty(EXHAUSTIVE); - } - - @Test - void beforeAndAfterTestMethodForDirtiesContextDeclaredLocallyOnClassBeforeEachTestMethod() throws Exception { - Class clazz = DirtiesContextDeclaredLocallyBeforeEachTestMethod.class; - BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); - given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("clean")); - beforeListener.beforeTestMethod(testContext); - afterListener.beforeTestMethod(testContext); - verify(testContext, times(1)).markApplicationContextDirty(EXHAUSTIVE); - afterListener.afterTestMethod(testContext); - beforeListener.afterTestMethod(testContext); - verify(testContext, times(1)).markApplicationContextDirty(EXHAUSTIVE); - } - - @Test - void beforeAndAfterTestMethodForDirtiesContextDeclaredLocallyOnClassAfterEachTestMethod() throws Exception { - Class clazz = DirtiesContextDeclaredLocallyAfterEachTestMethod.class; - BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); - given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("clean")); - beforeListener.beforeTestMethod(testContext); - afterListener.beforeTestMethod(testContext); - verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); - afterListener.afterTestMethod(testContext); - beforeListener.afterTestMethod(testContext); - verify(testContext, times(1)).markApplicationContextDirty(EXHAUSTIVE); - } - - @Test - void beforeAndAfterTestMethodForDirtiesContextDeclaredViaMetaAnnotationOnClassAfterEachTestMethod() - throws Exception { - Class clazz = DirtiesContextDeclaredViaMetaAnnotationAfterEachTestMethod.class; - BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); - given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("clean")); - beforeListener.beforeTestMethod(testContext); - afterListener.beforeTestMethod(testContext); - verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); - afterListener.afterTestMethod(testContext); - beforeListener.afterTestMethod(testContext); - verify(testContext, times(1)).markApplicationContextDirty(EXHAUSTIVE); - } - - @Test - void beforeAndAfterTestMethodForDirtiesContextDeclaredLocallyOnClassBeforeClass() throws Exception { - Class clazz = DirtiesContextDeclaredLocallyBeforeClass.class; - BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); - given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("clean")); - beforeListener.beforeTestMethod(testContext); - afterListener.beforeTestMethod(testContext); - afterListener.afterTestMethod(testContext); - beforeListener.afterTestMethod(testContext); - verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); - } - - @Test - void beforeAndAfterTestMethodForDirtiesContextDeclaredLocallyOnClassAfterClass() throws Exception { - Class clazz = DirtiesContextDeclaredLocallyAfterClass.class; - BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); - given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("clean")); - beforeListener.beforeTestMethod(testContext); - afterListener.beforeTestMethod(testContext); - afterListener.afterTestMethod(testContext); - beforeListener.afterTestMethod(testContext); - verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); - } - - @Test - void beforeAndAfterTestMethodForDirtiesContextDeclaredViaMetaAnnotationOnClassAfterClass() throws Exception { - Class clazz = DirtiesContextDeclaredViaMetaAnnotationAfterClass.class; - BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); - given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("clean")); - beforeListener.beforeTestMethod(testContext); - afterListener.beforeTestMethod(testContext); - afterListener.afterTestMethod(testContext); - beforeListener.afterTestMethod(testContext); - verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); - } - - @Test - void beforeAndAfterTestMethodForDirtiesContextViaMetaAnnotationWithOverrides() throws Exception { - Class clazz = DirtiesContextViaMetaAnnotationWithOverrides.class; - BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); - given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("clean")); - beforeListener.beforeTestMethod(testContext); - afterListener.beforeTestMethod(testContext); - verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); - afterListener.afterTestMethod(testContext); - beforeListener.afterTestMethod(testContext); - verify(testContext, times(1)).markApplicationContextDirty(CURRENT_LEVEL); - } - - // ------------------------------------------------------------------------- - - @Test - void beforeAndAfterTestClassForDirtiesContextDeclaredLocallyOnMethod() throws Exception { - Class clazz = getClass(); - BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); - beforeListener.beforeTestClass(testContext); - afterListener.beforeTestClass(testContext); - afterListener.afterTestClass(testContext); - beforeListener.afterTestClass(testContext); - verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); - } - - @Test - void beforeAndAfterTestClassForDirtiesContextDeclaredLocallyOnClassBeforeEachTestMethod() throws Exception { - Class clazz = DirtiesContextDeclaredLocallyBeforeEachTestMethod.class; - BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); - beforeListener.beforeTestClass(testContext); - afterListener.beforeTestClass(testContext); - afterListener.afterTestClass(testContext); - beforeListener.afterTestClass(testContext); - verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); - } - - @Test - void beforeAndAfterTestClassForDirtiesContextDeclaredLocallyOnClassAfterEachTestMethod() throws Exception { - Class clazz = DirtiesContextDeclaredLocallyAfterEachTestMethod.class; - BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); - beforeListener.beforeTestClass(testContext); - afterListener.beforeTestClass(testContext); - afterListener.afterTestClass(testContext); - beforeListener.afterTestClass(testContext); - verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); - } - - @Test - void beforeAndAfterTestClassForDirtiesContextDeclaredViaMetaAnnotationOnClassAfterEachTestMethod() - throws Exception { - Class clazz = DirtiesContextDeclaredViaMetaAnnotationAfterEachTestMethod.class; - BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); - beforeListener.beforeTestClass(testContext); - afterListener.beforeTestClass(testContext); - afterListener.afterTestClass(testContext); - beforeListener.afterTestClass(testContext); - verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); - } - - @Test - void beforeAndAfterTestClassForDirtiesContextDeclaredLocallyOnClassBeforeClass() throws Exception { - Class clazz = DirtiesContextDeclaredLocallyBeforeClass.class; + private void assertBeforeClass(Class clazz) throws Exception { BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); beforeListener.beforeTestClass(testContext); afterListener.beforeTestClass(testContext); @@ -246,45 +345,7 @@ class DirtiesContextTestExecutionListenerTests { verify(testContext, times(1)).markApplicationContextDirty(EXHAUSTIVE); } - @Test - void beforeAndAfterTestClassForDirtiesContextDeclaredLocallyOnClassAfterClass() throws Exception { - Class clazz = DirtiesContextDeclaredLocallyAfterClass.class; - BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); - beforeListener.beforeTestClass(testContext); - afterListener.beforeTestClass(testContext); - verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); - afterListener.afterTestClass(testContext); - beforeListener.afterTestClass(testContext); - verify(testContext, times(1)).markApplicationContextDirty(EXHAUSTIVE); - } - - @Test - void beforeAndAfterTestClassForDirtiesContextDeclaredViaMetaAnnotationOnClassAfterClass() throws Exception { - Class clazz = DirtiesContextDeclaredViaMetaAnnotationAfterClass.class; - BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); - beforeListener.beforeTestClass(testContext); - afterListener.beforeTestClass(testContext); - verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); - afterListener.afterTestClass(testContext); - beforeListener.afterTestClass(testContext); - verify(testContext, times(1)).markApplicationContextDirty(EXHAUSTIVE); - } - - @Test - void beforeAndAfterTestClassForDirtiesContextDeclaredViaMetaAnnotationWithOverrides() throws Exception { - Class clazz = DirtiesContextViaMetaAnnotationWithOverrides.class; - BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); - beforeListener.beforeTestClass(testContext); - afterListener.beforeTestClass(testContext); - afterListener.afterTestClass(testContext); - beforeListener.afterTestClass(testContext); - verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); - } - - @Test - void beforeAndAfterTestClassForDirtiesContextDeclaredViaMetaAnnotationWithOverriddenAttributes() - throws Exception { - Class clazz = DirtiesContextViaMetaAnnotationWithOverridenAttributes.class; + private void assertAfterClass(Class clazz) throws Exception { BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); beforeListener.beforeTestClass(testContext); afterListener.beforeTestClass(testContext); @@ -298,54 +359,49 @@ class DirtiesContextTestExecutionListenerTests { @DirtiesContext(methodMode = BEFORE_METHOD) void dirtiesContextDeclaredLocallyWithBeforeMethodMode() { - /* no-op */ } @DirtiesContext void dirtiesContextDeclaredLocallyWithAfterMethodMode() { - /* no-op */ } @MetaDirtyAfterMethod void dirtiesContextDeclaredViaMetaAnnotationWithAfterMethodMode() { - /* no-op */ } @DirtiesContext @Retention(RetentionPolicy.RUNTIME) - static @interface MetaDirtyAfterMethod { + @interface MetaDirtyAfterMethod { } @DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) @Retention(RetentionPolicy.RUNTIME) - static @interface MetaDirtyAfterEachTestMethod { + @interface MetaDirtyAfterEachTestMethod { } @DirtiesContext(classMode = AFTER_CLASS) @Retention(RetentionPolicy.RUNTIME) - static @interface MetaDirtyAfterClass { + @interface MetaDirtyAfterClass { } @DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) static class DirtiesContextDeclaredLocallyBeforeEachTestMethod { - void clean() { - /* no-op */ + void test() { } } @DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) static class DirtiesContextDeclaredLocallyAfterEachTestMethod { - void clean() { - /* no-op */ + void test() { } } @DirtiesContext @Retention(RetentionPolicy.RUNTIME) - static @interface MetaDirtyWithOverrides { + @interface MetaDirtyWithOverrides { ClassMode classMode() default AFTER_EACH_TEST_METHOD; @@ -355,48 +411,116 @@ class DirtiesContextTestExecutionListenerTests { @MetaDirtyAfterEachTestMethod static class DirtiesContextDeclaredViaMetaAnnotationAfterEachTestMethod { - void clean() { - /* no-op */ + void test() { } } @DirtiesContext(classMode = BEFORE_CLASS) static class DirtiesContextDeclaredLocallyBeforeClass { - void clean() { - /* no-op */ + void test() { } } @DirtiesContext(classMode = AFTER_CLASS) static class DirtiesContextDeclaredLocallyAfterClass { - void clean() { - /* no-op */ + void test() { } } @MetaDirtyAfterClass static class DirtiesContextDeclaredViaMetaAnnotationAfterClass { - void clean() { - /* no-op */ + void test() { } } @MetaDirtyWithOverrides static class DirtiesContextViaMetaAnnotationWithOverrides { - void clean() { - /* no-op */ + void test() { } } @MetaDirtyWithOverrides(classMode = AFTER_CLASS, hierarchyMode = EXHAUSTIVE) static class DirtiesContextViaMetaAnnotationWithOverridenAttributes { - void clean() { - /* no-op */ + void test() { + } + } + + @DirtiesContext(classMode = BEFORE_CLASS) + static class BeforeAndAfterTestClassTopLevelClass { + + void test() { + } + + + @DirtiesContext(classMode = AFTER_CLASS) + class ConfigOverriddenByDefault { + + void test() { + } + } + + @NestedTestConfiguration(INHERIT) + class InheritedConfig { + + void test() { + } + } + + @NestedTestConfiguration(OVERRIDE) + @DirtiesContext(classMode = AFTER_CLASS) + class OverriddenConfig { + + @NestedTestConfiguration(INHERIT) + @DirtiesContext(classMode = BEFORE_CLASS) + class InheritedConfigButOverridden { + + void test() { + } + } + } + } + + @DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) + static class BeforeAndAfterTestMethodTopLevelClass { + + void test() { + } + + + @DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) + class ConfigOverriddenByDefault { + + void test() { + } + } + + @NestedTestConfiguration(INHERIT) + class InheritedConfig { + + void test() { + } + } + + @NestedTestConfiguration(OVERRIDE) + @DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) + class OverriddenConfig { + + void test() { + } + + + @NestedTestConfiguration(INHERIT) + @DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) + class InheritedConfigButOverridden { + + void test() { + } + } } } diff --git a/spring-test/src/test/java/org/springframework/test/context/support/TestPropertySourceUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/support/TestPropertySourceUtilsTests.java index cf859b3111..385180cae2 100644 --- a/spring-test/src/test/java/org/springframework/test/context/support/TestPropertySourceUtilsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/TestPropertySourceUtilsTests.java @@ -20,6 +20,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Map; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.context.ConfigurableApplicationContext; @@ -80,6 +81,7 @@ class TestPropertySourceUtilsTests { } @Test + @Disabled("Validation for repeated @TestPropertySource annotations has been removed") void repeatedTestPropertySourcesWithConflictingInheritLocationsFlags() { assertThatIllegalArgumentException() .isThrownBy(() -> buildMergedTestPropertySources(RepeatedPropertySourcesWithConflictingInheritLocationsFlags.class)) @@ -89,6 +91,7 @@ class TestPropertySourceUtilsTests { } @Test + @Disabled("Validation for repeated @TestPropertySource annotations has been removed") void repeatedTestPropertySourcesWithConflictingInheritPropertiesFlags() { assertThatIllegalArgumentException() .isThrownBy(() -> buildMergedTestPropertySources(RepeatedPropertySourcesWithConflictingInheritPropertiesFlags.class)) diff --git a/spring-test/src/test/java/org/springframework/test/util/MetaAnnotationUtilsTests.java b/spring-test/src/test/java/org/springframework/test/util/MetaAnnotationUtilsTests.java index c0a7e22209..2deea9a7b6 100644 --- a/spring-test/src/test/java/org/springframework/test/util/MetaAnnotationUtilsTests.java +++ b/spring-test/src/test/java/org/springframework/test/util/MetaAnnotationUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -17,12 +17,13 @@ package org.springframework.test.util; import java.lang.annotation.Annotation; -import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.core.annotation.AnnotationUtils; @@ -47,489 +48,489 @@ import static org.springframework.test.util.MetaAnnotationUtils.findAnnotationDe */ class MetaAnnotationUtilsTests { - private void assertAtComponentOnComposedAnnotation( - Class rootDeclaringClass, String name, Class composedAnnotationType) { + @Nested + @DisplayName("findAnnotationDescriptor() tests") + class FindAnnotationDescriptorTests { - assertAtComponentOnComposedAnnotation(rootDeclaringClass, rootDeclaringClass, name, composedAnnotationType); - } + @Test + void findAnnotationDescriptorWithNoAnnotationPresent() { + assertThat(findAnnotationDescriptor(NonAnnotatedInterface.class, Transactional.class)).isNull(); + assertThat(findAnnotationDescriptor(NonAnnotatedClass.class, Transactional.class)).isNull(); + } - private void assertAtComponentOnComposedAnnotation( - Class startClass, Class rootDeclaringClass, String name, Class composedAnnotationType) { + @Test + void findAnnotationDescriptorWithInheritedAnnotationOnClass() { + // Note: @Transactional is inherited + assertThat(findAnnotationDescriptor(InheritedAnnotationClass.class, Transactional.class).getRootDeclaringClass()).isEqualTo(InheritedAnnotationClass.class); + assertThat(findAnnotationDescriptor(SubInheritedAnnotationClass.class, Transactional.class).getRootDeclaringClass()).isEqualTo(InheritedAnnotationClass.class); + } - assertAtComponentOnComposedAnnotation(startClass, rootDeclaringClass, composedAnnotationType, name, composedAnnotationType); - } + @Test + void findAnnotationDescriptorWithInheritedAnnotationOnInterface() { + // Note: @Transactional is inherited + Transactional rawAnnotation = InheritedAnnotationInterface.class.getAnnotation(Transactional.class); + AnnotationDescriptor descriptor = + findAnnotationDescriptor(InheritedAnnotationInterface.class, Transactional.class); + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(InheritedAnnotationInterface.class); + assertThat(descriptor.getDeclaringClass()).isEqualTo(InheritedAnnotationInterface.class); + assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); - private void assertAtComponentOnComposedAnnotation(Class startClass, Class rootDeclaringClass, - Class declaringClass, String name, Class composedAnnotationType) { + descriptor = findAnnotationDescriptor(SubInheritedAnnotationInterface.class, Transactional.class); + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(SubInheritedAnnotationInterface.class); + assertThat(descriptor.getDeclaringClass()).isEqualTo(InheritedAnnotationInterface.class); + assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); - AnnotationDescriptor descriptor = findAnnotationDescriptor(startClass, Component.class); - assertThat(descriptor).as("AnnotationDescriptor should not be null").isNotNull(); - assertThat(descriptor.getRootDeclaringClass()).as("rootDeclaringClass").isEqualTo(rootDeclaringClass); - assertThat(descriptor.getDeclaringClass()).as("declaringClass").isEqualTo(declaringClass); - assertThat(descriptor.getAnnotationType()).as("annotationType").isEqualTo(Component.class); - assertThat(descriptor.getAnnotation().value()).as("component name").isEqualTo(name); - assertThat(descriptor.getComposedAnnotation()).as("composedAnnotation should not be null").isNotNull(); - assertThat(descriptor.getComposedAnnotationType()).as("composedAnnotationType").isEqualTo(composedAnnotationType); - } + descriptor = findAnnotationDescriptor(SubSubInheritedAnnotationInterface.class, Transactional.class); + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(SubSubInheritedAnnotationInterface.class); + assertThat(descriptor.getDeclaringClass()).isEqualTo(InheritedAnnotationInterface.class); + assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); + } - private void assertAtComponentOnComposedAnnotationForMultipleCandidateTypes( - Class startClass, String name, Class composedAnnotationType) { + @Test + void findAnnotationDescriptorForNonInheritedAnnotationOnClass() { + // Note: @Order is not inherited. + assertThat(findAnnotationDescriptor(NonInheritedAnnotationClass.class, Order.class).getRootDeclaringClass()).isEqualTo(NonInheritedAnnotationClass.class); + assertThat(findAnnotationDescriptor(SubNonInheritedAnnotationClass.class, Order.class).getRootDeclaringClass()).isEqualTo(NonInheritedAnnotationClass.class); + } - assertAtComponentOnComposedAnnotationForMultipleCandidateTypes( - startClass, startClass, name, composedAnnotationType); - } + @Test + void findAnnotationDescriptorForNonInheritedAnnotationOnInterface() { + // Note: @Order is not inherited. + Order rawAnnotation = NonInheritedAnnotationInterface.class.getAnnotation(Order.class); - private void assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(Class startClass, - Class rootDeclaringClass, String name, Class composedAnnotationType) { + AnnotationDescriptor descriptor = + findAnnotationDescriptor(NonInheritedAnnotationInterface.class, Order.class); + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(NonInheritedAnnotationInterface.class); + assertThat(descriptor.getDeclaringClass()).isEqualTo(NonInheritedAnnotationInterface.class); + assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); - assertAtComponentOnComposedAnnotationForMultipleCandidateTypes( - startClass, rootDeclaringClass, composedAnnotationType, name, composedAnnotationType); - } + descriptor = findAnnotationDescriptor(SubNonInheritedAnnotationInterface.class, Order.class); + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(SubNonInheritedAnnotationInterface.class); + assertThat(descriptor.getDeclaringClass()).isEqualTo(NonInheritedAnnotationInterface.class); + assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); + } - @SuppressWarnings("unchecked") - private void assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(Class startClass, - Class rootDeclaringClass, Class declaringClass, String name, - Class composedAnnotationType) { + @Test + void findAnnotationDescriptorWithMetaComponentAnnotation() { + assertAtComponentOnComposedAnnotation(HasMetaComponentAnnotation.class, "meta1", Meta1.class); + } - Class annotationType = Component.class; - UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes( - startClass, Service.class, annotationType, Order.class, Transactional.class); - - assertThat(descriptor).as("UntypedAnnotationDescriptor should not be null").isNotNull(); - assertThat(descriptor.getRootDeclaringClass()).as("rootDeclaringClass").isEqualTo(rootDeclaringClass); - assertThat(descriptor.getDeclaringClass()).as("declaringClass").isEqualTo(declaringClass); - assertThat(descriptor.getAnnotationType()).as("annotationType").isEqualTo(annotationType); - assertThat(((Component) descriptor.getAnnotation()).value()).as("component name").isEqualTo(name); - assertThat(descriptor.getComposedAnnotation()).as("composedAnnotation should not be null").isNotNull(); - assertThat(descriptor.getComposedAnnotationType()).as("composedAnnotationType").isEqualTo(composedAnnotationType); - } - - @Test - void findAnnotationDescriptorWithNoAnnotationPresent() { - assertThat(findAnnotationDescriptor(NonAnnotatedInterface.class, Transactional.class)).isNull(); - assertThat(findAnnotationDescriptor(NonAnnotatedClass.class, Transactional.class)).isNull(); - } - - @Test - void findAnnotationDescriptorWithInheritedAnnotationOnClass() { - // Note: @Transactional is inherited - assertThat(findAnnotationDescriptor(InheritedAnnotationClass.class, Transactional.class).getRootDeclaringClass()).isEqualTo(InheritedAnnotationClass.class); - assertThat(findAnnotationDescriptor(SubInheritedAnnotationClass.class, Transactional.class).getRootDeclaringClass()).isEqualTo(InheritedAnnotationClass.class); - } - - @Test - void findAnnotationDescriptorWithInheritedAnnotationOnInterface() { - // Note: @Transactional is inherited - Transactional rawAnnotation = InheritedAnnotationInterface.class.getAnnotation(Transactional.class); - - AnnotationDescriptor descriptor = - findAnnotationDescriptor(InheritedAnnotationInterface.class, Transactional.class); - assertThat(descriptor).isNotNull(); - assertThat(descriptor.getRootDeclaringClass()).isEqualTo(InheritedAnnotationInterface.class); - assertThat(descriptor.getDeclaringClass()).isEqualTo(InheritedAnnotationInterface.class); - assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); - - descriptor = findAnnotationDescriptor(SubInheritedAnnotationInterface.class, Transactional.class); - assertThat(descriptor).isNotNull(); - assertThat(descriptor.getRootDeclaringClass()).isEqualTo(SubInheritedAnnotationInterface.class); - assertThat(descriptor.getDeclaringClass()).isEqualTo(InheritedAnnotationInterface.class); - assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); - - descriptor = findAnnotationDescriptor(SubSubInheritedAnnotationInterface.class, Transactional.class); - assertThat(descriptor).isNotNull(); - assertThat(descriptor.getRootDeclaringClass()).isEqualTo(SubSubInheritedAnnotationInterface.class); - assertThat(descriptor.getDeclaringClass()).isEqualTo(InheritedAnnotationInterface.class); - assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); - } - - @Test - void findAnnotationDescriptorForNonInheritedAnnotationOnClass() { - // Note: @Order is not inherited. - assertThat(findAnnotationDescriptor(NonInheritedAnnotationClass.class, Order.class).getRootDeclaringClass()).isEqualTo(NonInheritedAnnotationClass.class); - assertThat(findAnnotationDescriptor(SubNonInheritedAnnotationClass.class, Order.class).getRootDeclaringClass()).isEqualTo(NonInheritedAnnotationClass.class); - } - - @Test - void findAnnotationDescriptorForNonInheritedAnnotationOnInterface() { - // Note: @Order is not inherited. - Order rawAnnotation = NonInheritedAnnotationInterface.class.getAnnotation(Order.class); - - AnnotationDescriptor descriptor = - findAnnotationDescriptor(NonInheritedAnnotationInterface.class, Order.class); - assertThat(descriptor).isNotNull(); - assertThat(descriptor.getRootDeclaringClass()).isEqualTo(NonInheritedAnnotationInterface.class); - assertThat(descriptor.getDeclaringClass()).isEqualTo(NonInheritedAnnotationInterface.class); - assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); - - descriptor = findAnnotationDescriptor(SubNonInheritedAnnotationInterface.class, Order.class); - assertThat(descriptor).isNotNull(); - assertThat(descriptor.getRootDeclaringClass()).isEqualTo(SubNonInheritedAnnotationInterface.class); - assertThat(descriptor.getDeclaringClass()).isEqualTo(NonInheritedAnnotationInterface.class); - assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); - } - - @Test - void findAnnotationDescriptorWithMetaComponentAnnotation() { - assertAtComponentOnComposedAnnotation(HasMetaComponentAnnotation.class, "meta1", Meta1.class); - } - - @Test - void findAnnotationDescriptorWithLocalAndMetaComponentAnnotation() { - Class annotationType = Component.class; - AnnotationDescriptor descriptor = findAnnotationDescriptor( + @Test + void findAnnotationDescriptorWithLocalAndMetaComponentAnnotation() { + Class annotationType = Component.class; + AnnotationDescriptor descriptor = findAnnotationDescriptor( HasLocalAndMetaComponentAnnotation.class, annotationType); - assertThat(descriptor.getRootDeclaringClass()).isEqualTo(HasLocalAndMetaComponentAnnotation.class); - assertThat(descriptor.getAnnotationType()).isEqualTo(annotationType); - assertThat(descriptor.getComposedAnnotation()).isNull(); - assertThat(descriptor.getComposedAnnotationType()).isNull(); - } + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(HasLocalAndMetaComponentAnnotation.class); + assertThat(descriptor.getAnnotationType()).isEqualTo(annotationType); + assertThat(descriptor.getComposedAnnotation()).isNull(); + assertThat(descriptor.getComposedAnnotationType()).isNull(); + } - @Test - void findAnnotationDescriptorForInterfaceWithMetaAnnotation() { - assertAtComponentOnComposedAnnotation(InterfaceWithMetaAnnotation.class, "meta1", Meta1.class); - } + @Test + void findAnnotationDescriptorForInterfaceWithMetaAnnotation() { + assertAtComponentOnComposedAnnotation(InterfaceWithMetaAnnotation.class, "meta1", Meta1.class); + } - @Test - void findAnnotationDescriptorForClassWithMetaAnnotatedInterface() { - Component rawAnnotation = AnnotationUtils.findAnnotation(ClassWithMetaAnnotatedInterface.class, Component.class); - AnnotationDescriptor descriptor = - findAnnotationDescriptor(ClassWithMetaAnnotatedInterface.class, Component.class); + @Test + void findAnnotationDescriptorForClassWithMetaAnnotatedInterface() { + Component rawAnnotation = AnnotationUtils.findAnnotation(ClassWithMetaAnnotatedInterface.class, Component.class); + AnnotationDescriptor descriptor = + findAnnotationDescriptor(ClassWithMetaAnnotatedInterface.class, Component.class); - assertThat(descriptor).isNotNull(); - assertThat(descriptor.getRootDeclaringClass()).isEqualTo(ClassWithMetaAnnotatedInterface.class); - assertThat(descriptor.getDeclaringClass()).isEqualTo(Meta1.class); - assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); - assertThat(descriptor.getComposedAnnotation().annotationType()).isEqualTo(Meta1.class); - } + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(ClassWithMetaAnnotatedInterface.class); + assertThat(descriptor.getDeclaringClass()).isEqualTo(Meta1.class); + assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); + assertThat(descriptor.getComposedAnnotation().annotationType()).isEqualTo(Meta1.class); + } - @Test - void findAnnotationDescriptorForClassWithLocalMetaAnnotationAndAnnotatedSuperclass() { - AnnotationDescriptor descriptor = findAnnotationDescriptor( + @Test + void findAnnotationDescriptorForClassWithLocalMetaAnnotationAndAnnotatedSuperclass() { + AnnotationDescriptor descriptor = findAnnotationDescriptor( MetaAnnotatedAndSuperAnnotatedContextConfigClass.class, ContextConfiguration.class); - assertThat(descriptor).as("AnnotationDescriptor should not be null").isNotNull(); - assertThat(descriptor.getRootDeclaringClass()).as("rootDeclaringClass").isEqualTo(MetaAnnotatedAndSuperAnnotatedContextConfigClass.class); - assertThat(descriptor.getDeclaringClass()).as("declaringClass").isEqualTo(MetaConfig.class); - assertThat(descriptor.getAnnotationType()).as("annotationType").isEqualTo(ContextConfiguration.class); - assertThat(descriptor.getComposedAnnotation()).as("composedAnnotation should not be null").isNotNull(); - assertThat(descriptor.getComposedAnnotationType()).as("composedAnnotationType").isEqualTo(MetaConfig.class); + assertThat(descriptor).as("AnnotationDescriptor should not be null").isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).as("rootDeclaringClass").isEqualTo(MetaAnnotatedAndSuperAnnotatedContextConfigClass.class); + assertThat(descriptor.getDeclaringClass()).as("declaringClass").isEqualTo(MetaConfig.class); + assertThat(descriptor.getAnnotationType()).as("annotationType").isEqualTo(ContextConfiguration.class); + assertThat(descriptor.getComposedAnnotation()).as("composedAnnotation should not be null").isNotNull(); + assertThat(descriptor.getComposedAnnotationType()).as("composedAnnotationType").isEqualTo(MetaConfig.class); - assertThat(descriptor.getAnnotationAttributes().getClassArray("classes")).as("configured classes").isEqualTo(new Class[] {String.class}); - } + assertThat(descriptor.getAnnotationAttributes().getClassArray("classes")).as("configured classes").isEqualTo(new Class[] {String.class}); + } - @Test - void findAnnotationDescriptorForClassWithLocalMetaAnnotationAndMetaAnnotatedInterface() { - assertAtComponentOnComposedAnnotation(ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, "meta2", Meta2.class); - } + @Test + void findAnnotationDescriptorForClassWithLocalMetaAnnotationAndMetaAnnotatedInterface() { + assertAtComponentOnComposedAnnotation(ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, "meta2", Meta2.class); + } - @Test - void findAnnotationDescriptorForSubClassWithLocalMetaAnnotationAndMetaAnnotatedInterface() { - assertAtComponentOnComposedAnnotation(SubClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, + @Test + void findAnnotationDescriptorForSubClassWithLocalMetaAnnotationAndMetaAnnotatedInterface() { + assertAtComponentOnComposedAnnotation(SubClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, "meta2", Meta2.class); - } + } - /** - * @since 4.0.3 - */ - @Test - void findAnnotationDescriptorOnMetaMetaAnnotatedClass() { - Class startClass = MetaMetaAnnotatedClass.class; - assertAtComponentOnComposedAnnotation(startClass, startClass, Meta2.class, "meta2", MetaMeta.class); - } + /** + * @since 4.0.3 + */ + @Test + void findAnnotationDescriptorOnMetaMetaAnnotatedClass() { + Class startClass = MetaMetaAnnotatedClass.class; + assertAtComponentOnComposedAnnotation(startClass, startClass, Meta2.class, "meta2", MetaMeta.class); + } - /** - * @since 4.0.3 - */ - @Test - void findAnnotationDescriptorOnMetaMetaMetaAnnotatedClass() { - Class startClass = MetaMetaMetaAnnotatedClass.class; - assertAtComponentOnComposedAnnotation(startClass, startClass, Meta2.class, "meta2", MetaMetaMeta.class); - } + /** + * @since 4.0.3 + */ + @Test + void findAnnotationDescriptorOnMetaMetaMetaAnnotatedClass() { + Class startClass = MetaMetaMetaAnnotatedClass.class; + assertAtComponentOnComposedAnnotation(startClass, startClass, Meta2.class, "meta2", MetaMetaMeta.class); + } - /** - * @since 4.0.3 - */ - @Test - void findAnnotationDescriptorOnAnnotatedClassWithMissingTargetMetaAnnotation() { - // InheritedAnnotationClass is NOT annotated or meta-annotated with @Component - AnnotationDescriptor descriptor = findAnnotationDescriptor( + /** + * @since 4.0.3 + */ + @Test + void findAnnotationDescriptorOnAnnotatedClassWithMissingTargetMetaAnnotation() { + // InheritedAnnotationClass is NOT annotated or meta-annotated with @Component + AnnotationDescriptor descriptor = findAnnotationDescriptor( InheritedAnnotationClass.class, Component.class); - assertThat(descriptor).as("Should not find @Component on InheritedAnnotationClass").isNull(); - } + assertThat(descriptor).as("Should not find @Component on InheritedAnnotationClass").isNull(); + } - /** - * @since 4.0.3 - */ - @Test - void findAnnotationDescriptorOnMetaCycleAnnotatedClassWithMissingTargetMetaAnnotation() { - AnnotationDescriptor descriptor = findAnnotationDescriptor( + /** + * @since 4.0.3 + */ + @Test + void findAnnotationDescriptorOnMetaCycleAnnotatedClassWithMissingTargetMetaAnnotation() { + AnnotationDescriptor descriptor = findAnnotationDescriptor( MetaCycleAnnotatedClass.class, Component.class); - assertThat(descriptor).as("Should not find @Component on MetaCycleAnnotatedClass").isNull(); + assertThat(descriptor).as("Should not find @Component on MetaCycleAnnotatedClass").isNull(); + } + + private void assertAtComponentOnComposedAnnotation( + Class rootDeclaringClass, String name, Class composedAnnotationType) { + + assertAtComponentOnComposedAnnotation(rootDeclaringClass, rootDeclaringClass, name, composedAnnotationType); + } + + private void assertAtComponentOnComposedAnnotation( + Class startClass, Class rootDeclaringClass, String name, Class composedAnnotationType) { + + assertAtComponentOnComposedAnnotation(startClass, rootDeclaringClass, composedAnnotationType, name, composedAnnotationType); + } + + private void assertAtComponentOnComposedAnnotation(Class startClass, Class rootDeclaringClass, + Class declaringClass, String name, Class composedAnnotationType) { + + AnnotationDescriptor descriptor = findAnnotationDescriptor(startClass, Component.class); + assertThat(descriptor).as("AnnotationDescriptor should not be null").isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).as("rootDeclaringClass").isEqualTo(rootDeclaringClass); + assertThat(descriptor.getDeclaringClass()).as("declaringClass").isEqualTo(declaringClass); + assertThat(descriptor.getAnnotationType()).as("annotationType").isEqualTo(Component.class); + assertThat(descriptor.getAnnotation().value()).as("component name").isEqualTo(name); + assertThat(descriptor.getComposedAnnotation()).as("composedAnnotation should not be null").isNotNull(); + assertThat(descriptor.getComposedAnnotationType()).as("composedAnnotationType").isEqualTo(composedAnnotationType); + } + } - // ------------------------------------------------------------------------- + @Nested + @DisplayName("findAnnotationDescriptorForTypes() tests") + class FindAnnotationDescriptorForTypesTests { + + @Test + @SuppressWarnings("unchecked") + void findAnnotationDescriptorForTypesWithNoAnnotationPresent() { + assertThat(findAnnotationDescriptorForTypes(NonAnnotatedInterface.class, Transactional.class, Component.class)).isNull(); + assertThat(findAnnotationDescriptorForTypes(NonAnnotatedClass.class, Transactional.class, Order.class)).isNull(); + } + + @Test + @SuppressWarnings("unchecked") + void findAnnotationDescriptorForTypesWithInheritedAnnotationOnClass() { + // Note: @Transactional is inherited + assertThat(findAnnotationDescriptorForTypes(InheritedAnnotationClass.class, Transactional.class).getRootDeclaringClass()).isEqualTo(InheritedAnnotationClass.class); + assertThat(findAnnotationDescriptorForTypes(SubInheritedAnnotationClass.class, Transactional.class).getRootDeclaringClass()).isEqualTo(InheritedAnnotationClass.class); + } + + @Test + @SuppressWarnings("unchecked") + void findAnnotationDescriptorForTypesWithInheritedAnnotationOnInterface() { + // Note: @Transactional is inherited + Transactional rawAnnotation = InheritedAnnotationInterface.class.getAnnotation(Transactional.class); + + UntypedAnnotationDescriptor descriptor = + findAnnotationDescriptorForTypes(InheritedAnnotationInterface.class, Transactional.class); + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(InheritedAnnotationInterface.class); + assertThat(descriptor.getDeclaringClass()).isEqualTo(InheritedAnnotationInterface.class); + assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); + + descriptor = findAnnotationDescriptorForTypes(SubInheritedAnnotationInterface.class, Transactional.class); + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(SubInheritedAnnotationInterface.class); + assertThat(descriptor.getDeclaringClass()).isEqualTo(InheritedAnnotationInterface.class); + assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); + + descriptor = findAnnotationDescriptorForTypes(SubSubInheritedAnnotationInterface.class, Transactional.class); + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(SubSubInheritedAnnotationInterface.class); + assertThat(descriptor.getDeclaringClass()).isEqualTo(InheritedAnnotationInterface.class); + assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); + } + + @Test + @SuppressWarnings("unchecked") + void findAnnotationDescriptorForTypesForNonInheritedAnnotationOnClass() { + // Note: @Order is not inherited. + assertThat(findAnnotationDescriptorForTypes(NonInheritedAnnotationClass.class, Order.class).getRootDeclaringClass()).isEqualTo(NonInheritedAnnotationClass.class); + assertThat(findAnnotationDescriptorForTypes(SubNonInheritedAnnotationClass.class, Order.class).getRootDeclaringClass()).isEqualTo(NonInheritedAnnotationClass.class); + } + + @Test + @SuppressWarnings("unchecked") + void findAnnotationDescriptorForTypesForNonInheritedAnnotationOnInterface() { + // Note: @Order is not inherited. + Order rawAnnotation = NonInheritedAnnotationInterface.class.getAnnotation(Order.class); + + UntypedAnnotationDescriptor descriptor = + findAnnotationDescriptorForTypes(NonInheritedAnnotationInterface.class, Order.class); + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(NonInheritedAnnotationInterface.class); + assertThat(descriptor.getDeclaringClass()).isEqualTo(NonInheritedAnnotationInterface.class); + assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); + + descriptor = findAnnotationDescriptorForTypes(SubNonInheritedAnnotationInterface.class, Order.class); + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(SubNonInheritedAnnotationInterface.class); + assertThat(descriptor.getDeclaringClass()).isEqualTo(NonInheritedAnnotationInterface.class); + assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); + } + + @Test + @SuppressWarnings("unchecked") + void findAnnotationDescriptorForTypesWithLocalAndMetaComponentAnnotation() { + Class annotationType = Component.class; + UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes( + HasLocalAndMetaComponentAnnotation.class, Transactional.class, annotationType, Order.class); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(HasLocalAndMetaComponentAnnotation.class); + assertThat(descriptor.getAnnotationType()).isEqualTo(annotationType); + assertThat(descriptor.getComposedAnnotation()).isNull(); + assertThat(descriptor.getComposedAnnotationType()).isNull(); + } + + @Test + void findAnnotationDescriptorForTypesWithMetaComponentAnnotation() { + Class startClass = HasMetaComponentAnnotation.class; + assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(startClass, "meta1", Meta1.class); + } + + @Test + @SuppressWarnings("unchecked") + void findAnnotationDescriptorForTypesWithMetaAnnotationWithDefaultAttributes() { + Class startClass = MetaConfigWithDefaultAttributesTestCase.class; + Class annotationType = ContextConfiguration.class; + + UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes(startClass, + Service.class, ContextConfiguration.class, Order.class, Transactional.class); + + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(startClass); + assertThat(descriptor.getAnnotationType()).isEqualTo(annotationType); + assertThat(((ContextConfiguration) descriptor.getAnnotation()).value()).isEqualTo(new Class[] {}); + assertThat(descriptor.getAnnotationAttributes().getClassArray("classes")).isEqualTo(new Class[] {MetaConfig.DevConfig.class, MetaConfig.ProductionConfig.class}); + assertThat(descriptor.getComposedAnnotation()).isNotNull(); + assertThat(descriptor.getComposedAnnotationType()).isEqualTo(MetaConfig.class); + } + + @Test + @SuppressWarnings("unchecked") + void findAnnotationDescriptorForTypesWithMetaAnnotationWithOverriddenAttributes() { + Class startClass = MetaConfigWithOverriddenAttributesTestCase.class; + Class annotationType = ContextConfiguration.class; + + UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes( + startClass, Service.class, ContextConfiguration.class, Order.class, Transactional.class); + + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(startClass); + assertThat(descriptor.getAnnotationType()).isEqualTo(annotationType); + assertThat(((ContextConfiguration) descriptor.getAnnotation()).value()).isEqualTo(new Class[] {}); + assertThat(descriptor.getAnnotationAttributes().getClassArray("classes")).isEqualTo(new Class[] {MetaAnnotationUtilsTests.class}); + assertThat(descriptor.getComposedAnnotation()).isNotNull(); + assertThat(descriptor.getComposedAnnotationType()).isEqualTo(MetaConfig.class); + } + + @Test + void findAnnotationDescriptorForTypesForInterfaceWithMetaAnnotation() { + Class startClass = InterfaceWithMetaAnnotation.class; + assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(startClass, "meta1", Meta1.class); + } + + @Test + @SuppressWarnings("unchecked") + void findAnnotationDescriptorForTypesForClassWithMetaAnnotatedInterface() { + Component rawAnnotation = AnnotationUtils.findAnnotation(ClassWithMetaAnnotatedInterface.class, Component.class); + + UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes( + ClassWithMetaAnnotatedInterface.class, Service.class, Component.class, Order.class, Transactional.class); + + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(ClassWithMetaAnnotatedInterface.class); + assertThat(descriptor.getDeclaringClass()).isEqualTo(Meta1.class); + assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); + assertThat(descriptor.getComposedAnnotation().annotationType()).isEqualTo(Meta1.class); + } + + @Test + void findAnnotationDescriptorForTypesForClassWithLocalMetaAnnotationAndMetaAnnotatedInterface() { + Class startClass = ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class; + assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(startClass, "meta2", Meta2.class); + } + + @Test + void findAnnotationDescriptorForTypesForSubClassWithLocalMetaAnnotationAndMetaAnnotatedInterface() { + assertAtComponentOnComposedAnnotationForMultipleCandidateTypes( + SubClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, + ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, "meta2", Meta2.class); + } + + /** + * @since 4.0.3 + */ + @Test + void findAnnotationDescriptorForTypesOnMetaMetaAnnotatedClass() { + Class startClass = MetaMetaAnnotatedClass.class; + assertAtComponentOnComposedAnnotationForMultipleCandidateTypes( + startClass, startClass, Meta2.class, "meta2", MetaMeta.class); + } + + /** + * @since 4.0.3 + */ + @Test + void findAnnotationDescriptorForTypesOnMetaMetaMetaAnnotatedClass() { + Class startClass = MetaMetaMetaAnnotatedClass.class; + assertAtComponentOnComposedAnnotationForMultipleCandidateTypes( + startClass, startClass, Meta2.class, "meta2", MetaMetaMeta.class); + } + + /** + * @since 4.0.3 + */ + @Test + @SuppressWarnings("unchecked") + void findAnnotationDescriptorForTypesOnAnnotatedClassWithMissingTargetMetaAnnotation() { + // InheritedAnnotationClass is NOT annotated or meta-annotated with @Component, + // @Service, or @Order, but it is annotated with @Transactional. + UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes( + InheritedAnnotationClass.class, Service.class, Component.class, Order.class); + assertThat(descriptor).as("Should not find @Component on InheritedAnnotationClass").isNull(); + } + + /** + * @since 4.0.3 + */ + @Test + @SuppressWarnings("unchecked") + void findAnnotationDescriptorForTypesOnMetaCycleAnnotatedClassWithMissingTargetMetaAnnotation() { + UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes( + MetaCycleAnnotatedClass.class, Service.class, Component.class, Order.class); + assertThat(descriptor).as("Should not find @Component on MetaCycleAnnotatedClass").isNull(); + } + + private void assertAtComponentOnComposedAnnotationForMultipleCandidateTypes( + Class startClass, String name, Class composedAnnotationType) { + + assertAtComponentOnComposedAnnotationForMultipleCandidateTypes( + startClass, startClass, name, composedAnnotationType); + } + + private void assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(Class startClass, + Class rootDeclaringClass, String name, Class composedAnnotationType) { + + assertAtComponentOnComposedAnnotationForMultipleCandidateTypes( + startClass, rootDeclaringClass, composedAnnotationType, name, composedAnnotationType); + } + + @SuppressWarnings("unchecked") + private void assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(Class startClass, + Class rootDeclaringClass, Class declaringClass, String name, + Class composedAnnotationType) { + + Class annotationType = Component.class; + UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes( + startClass, Service.class, annotationType, Order.class, Transactional.class); + + assertThat(descriptor).as("UntypedAnnotationDescriptor should not be null").isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).as("rootDeclaringClass").isEqualTo(rootDeclaringClass); + assertThat(descriptor.getDeclaringClass()).as("declaringClass").isEqualTo(declaringClass); + assertThat(descriptor.getAnnotationType()).as("annotationType").isEqualTo(annotationType); + assertThat(((Component) descriptor.getAnnotation()).value()).as("component name").isEqualTo(name); + assertThat(descriptor.getComposedAnnotation()).as("composedAnnotation should not be null").isNotNull(); + assertThat(descriptor.getComposedAnnotationType()).as("composedAnnotationType").isEqualTo(composedAnnotationType); + } - @Test - @SuppressWarnings("unchecked") - void findAnnotationDescriptorForTypesWithNoAnnotationPresent() { - assertThat(findAnnotationDescriptorForTypes(NonAnnotatedInterface.class, Transactional.class, Component.class)).isNull(); - assertThat(findAnnotationDescriptorForTypes(NonAnnotatedClass.class, Transactional.class, Order.class)).isNull(); } - @Test - @SuppressWarnings("unchecked") - void findAnnotationDescriptorForTypesWithInheritedAnnotationOnClass() { - // Note: @Transactional is inherited - assertThat(findAnnotationDescriptorForTypes(InheritedAnnotationClass.class, Transactional.class).getRootDeclaringClass()).isEqualTo(InheritedAnnotationClass.class); - assertThat(findAnnotationDescriptorForTypes(SubInheritedAnnotationClass.class, Transactional.class).getRootDeclaringClass()).isEqualTo(InheritedAnnotationClass.class); - } - - @Test - @SuppressWarnings("unchecked") - void findAnnotationDescriptorForTypesWithInheritedAnnotationOnInterface() { - // Note: @Transactional is inherited - Transactional rawAnnotation = InheritedAnnotationInterface.class.getAnnotation(Transactional.class); - - UntypedAnnotationDescriptor descriptor = - findAnnotationDescriptorForTypes(InheritedAnnotationInterface.class, Transactional.class); - assertThat(descriptor).isNotNull(); - assertThat(descriptor.getRootDeclaringClass()).isEqualTo(InheritedAnnotationInterface.class); - assertThat(descriptor.getDeclaringClass()).isEqualTo(InheritedAnnotationInterface.class); - assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); - - descriptor = findAnnotationDescriptorForTypes(SubInheritedAnnotationInterface.class, Transactional.class); - assertThat(descriptor).isNotNull(); - assertThat(descriptor.getRootDeclaringClass()).isEqualTo(SubInheritedAnnotationInterface.class); - assertThat(descriptor.getDeclaringClass()).isEqualTo(InheritedAnnotationInterface.class); - assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); - - descriptor = findAnnotationDescriptorForTypes(SubSubInheritedAnnotationInterface.class, Transactional.class); - assertThat(descriptor).isNotNull(); - assertThat(descriptor.getRootDeclaringClass()).isEqualTo(SubSubInheritedAnnotationInterface.class); - assertThat(descriptor.getDeclaringClass()).isEqualTo(InheritedAnnotationInterface.class); - assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); - } - - @Test - @SuppressWarnings("unchecked") - void findAnnotationDescriptorForTypesForNonInheritedAnnotationOnClass() { - // Note: @Order is not inherited. - assertThat(findAnnotationDescriptorForTypes(NonInheritedAnnotationClass.class, Order.class).getRootDeclaringClass()).isEqualTo(NonInheritedAnnotationClass.class); - assertThat(findAnnotationDescriptorForTypes(SubNonInheritedAnnotationClass.class, Order.class).getRootDeclaringClass()).isEqualTo(NonInheritedAnnotationClass.class); - } - - @Test - @SuppressWarnings("unchecked") - void findAnnotationDescriptorForTypesForNonInheritedAnnotationOnInterface() { - // Note: @Order is not inherited. - Order rawAnnotation = NonInheritedAnnotationInterface.class.getAnnotation(Order.class); - - UntypedAnnotationDescriptor descriptor = - findAnnotationDescriptorForTypes(NonInheritedAnnotationInterface.class, Order.class); - assertThat(descriptor).isNotNull(); - assertThat(descriptor.getRootDeclaringClass()).isEqualTo(NonInheritedAnnotationInterface.class); - assertThat(descriptor.getDeclaringClass()).isEqualTo(NonInheritedAnnotationInterface.class); - assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); - - descriptor = findAnnotationDescriptorForTypes(SubNonInheritedAnnotationInterface.class, Order.class); - assertThat(descriptor).isNotNull(); - assertThat(descriptor.getRootDeclaringClass()).isEqualTo(SubNonInheritedAnnotationInterface.class); - assertThat(descriptor.getDeclaringClass()).isEqualTo(NonInheritedAnnotationInterface.class); - assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); - } - - @Test - @SuppressWarnings("unchecked") - void findAnnotationDescriptorForTypesWithLocalAndMetaComponentAnnotation() { - Class annotationType = Component.class; - UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes( - HasLocalAndMetaComponentAnnotation.class, Transactional.class, annotationType, Order.class); - assertThat(descriptor.getRootDeclaringClass()).isEqualTo(HasLocalAndMetaComponentAnnotation.class); - assertThat(descriptor.getAnnotationType()).isEqualTo(annotationType); - assertThat(descriptor.getComposedAnnotation()).isNull(); - assertThat(descriptor.getComposedAnnotationType()).isNull(); - } - - @Test - void findAnnotationDescriptorForTypesWithMetaComponentAnnotation() { - Class startClass = HasMetaComponentAnnotation.class; - assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(startClass, "meta1", Meta1.class); - } - - @Test - @SuppressWarnings("unchecked") - void findAnnotationDescriptorForTypesWithMetaAnnotationWithDefaultAttributes() { - Class startClass = MetaConfigWithDefaultAttributesTestCase.class; - Class annotationType = ContextConfiguration.class; - - UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes(startClass, - Service.class, ContextConfiguration.class, Order.class, Transactional.class); - - assertThat(descriptor).isNotNull(); - assertThat(descriptor.getRootDeclaringClass()).isEqualTo(startClass); - assertThat(descriptor.getAnnotationType()).isEqualTo(annotationType); - assertThat(((ContextConfiguration) descriptor.getAnnotation()).value()).isEqualTo(new Class[] {}); - assertThat(descriptor.getAnnotationAttributes().getClassArray("classes")).isEqualTo(new Class[] {MetaConfig.DevConfig.class, MetaConfig.ProductionConfig.class}); - assertThat(descriptor.getComposedAnnotation()).isNotNull(); - assertThat(descriptor.getComposedAnnotationType()).isEqualTo(MetaConfig.class); - } - - @Test - @SuppressWarnings("unchecked") - void findAnnotationDescriptorForTypesWithMetaAnnotationWithOverriddenAttributes() { - Class startClass = MetaConfigWithOverriddenAttributesTestCase.class; - Class annotationType = ContextConfiguration.class; - - UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes( - startClass, Service.class, ContextConfiguration.class, Order.class, Transactional.class); - - assertThat(descriptor).isNotNull(); - assertThat(descriptor.getRootDeclaringClass()).isEqualTo(startClass); - assertThat(descriptor.getAnnotationType()).isEqualTo(annotationType); - assertThat(((ContextConfiguration) descriptor.getAnnotation()).value()).isEqualTo(new Class[] {}); - assertThat(descriptor.getAnnotationAttributes().getClassArray("classes")).isEqualTo(new Class[] {MetaAnnotationUtilsTests.class}); - assertThat(descriptor.getComposedAnnotation()).isNotNull(); - assertThat(descriptor.getComposedAnnotationType()).isEqualTo(MetaConfig.class); - } - - @Test - void findAnnotationDescriptorForTypesForInterfaceWithMetaAnnotation() { - Class startClass = InterfaceWithMetaAnnotation.class; - assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(startClass, "meta1", Meta1.class); - } - - @Test - @SuppressWarnings("unchecked") - void findAnnotationDescriptorForTypesForClassWithMetaAnnotatedInterface() { - Component rawAnnotation = AnnotationUtils.findAnnotation(ClassWithMetaAnnotatedInterface.class, Component.class); - - UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes( - ClassWithMetaAnnotatedInterface.class, Service.class, Component.class, Order.class, Transactional.class); - - assertThat(descriptor).isNotNull(); - assertThat(descriptor.getRootDeclaringClass()).isEqualTo(ClassWithMetaAnnotatedInterface.class); - assertThat(descriptor.getDeclaringClass()).isEqualTo(Meta1.class); - assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); - assertThat(descriptor.getComposedAnnotation().annotationType()).isEqualTo(Meta1.class); - } - - @Test - void findAnnotationDescriptorForTypesForClassWithLocalMetaAnnotationAndMetaAnnotatedInterface() { - Class startClass = ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class; - assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(startClass, "meta2", Meta2.class); - } - - @Test - void findAnnotationDescriptorForTypesForSubClassWithLocalMetaAnnotationAndMetaAnnotatedInterface() { - assertAtComponentOnComposedAnnotationForMultipleCandidateTypes( - SubClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, - ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, "meta2", Meta2.class); - } - - /** - * @since 4.0.3 - */ - @Test - void findAnnotationDescriptorForTypesOnMetaMetaAnnotatedClass() { - Class startClass = MetaMetaAnnotatedClass.class; - assertAtComponentOnComposedAnnotationForMultipleCandidateTypes( - startClass, startClass, Meta2.class, "meta2", MetaMeta.class); - } - - /** - * @since 4.0.3 - */ - @Test - void findAnnotationDescriptorForTypesOnMetaMetaMetaAnnotatedClass() { - Class startClass = MetaMetaMetaAnnotatedClass.class; - assertAtComponentOnComposedAnnotationForMultipleCandidateTypes( - startClass, startClass, Meta2.class, "meta2", MetaMetaMeta.class); - } - - /** - * @since 4.0.3 - */ - @Test - @SuppressWarnings("unchecked") - void findAnnotationDescriptorForTypesOnAnnotatedClassWithMissingTargetMetaAnnotation() { - // InheritedAnnotationClass is NOT annotated or meta-annotated with @Component, - // @Service, or @Order, but it is annotated with @Transactional. - UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes( - InheritedAnnotationClass.class, Service.class, Component.class, Order.class); - assertThat(descriptor).as("Should not find @Component on InheritedAnnotationClass").isNull(); - } - - /** - * @since 4.0.3 - */ - @Test - @SuppressWarnings("unchecked") - void findAnnotationDescriptorForTypesOnMetaCycleAnnotatedClassWithMissingTargetMetaAnnotation() { - UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes( - MetaCycleAnnotatedClass.class, Service.class, Component.class, Order.class); - assertThat(descriptor).as("Should not find @Component on MetaCycleAnnotatedClass").isNull(); - } - - // ------------------------------------------------------------------------- @Component(value = "meta1") @Order @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) - @Documented - static @interface Meta1 { + @interface Meta1 { } @Component(value = "meta2") @Transactional @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) - @Documented - static @interface Meta2 { + @interface Meta2 { } @Meta2 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) - @Documented @interface MetaMeta { } @MetaMeta @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) - @Documented @interface MetaMetaMeta { } @MetaCycle3 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) - @Documented @interface MetaCycle1 { } @MetaCycle1 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) - @Documented @interface MetaCycle2 { } @MetaCycle2 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) - @Documented @interface MetaCycle3 { } @ContextConfiguration @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) - @Documented - static @interface MetaConfig { + @interface MetaConfig { static class DevConfig { } @@ -554,7 +555,7 @@ class MetaAnnotationUtilsTests { } @Meta1 - static interface InterfaceWithMetaAnnotation { + interface InterfaceWithMetaAnnotation { } static class ClassWithMetaAnnotatedInterface implements InterfaceWithMetaAnnotation { @@ -591,26 +592,26 @@ class MetaAnnotationUtilsTests { // ------------------------------------------------------------------------- @Transactional - static interface InheritedAnnotationInterface { + interface InheritedAnnotationInterface { } - static interface SubInheritedAnnotationInterface extends InheritedAnnotationInterface { + interface SubInheritedAnnotationInterface extends InheritedAnnotationInterface { } - static interface SubSubInheritedAnnotationInterface extends SubInheritedAnnotationInterface { + interface SubSubInheritedAnnotationInterface extends SubInheritedAnnotationInterface { } @Order - static interface NonInheritedAnnotationInterface { + interface NonInheritedAnnotationInterface { } - static interface SubNonInheritedAnnotationInterface extends NonInheritedAnnotationInterface { + interface SubNonInheritedAnnotationInterface extends NonInheritedAnnotationInterface { } static class NonAnnotatedClass { } - static interface NonAnnotatedInterface { + interface NonAnnotatedInterface { } @Transactional