From 903e9f2a0269a7015435ad98081a4fed60168a04 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Wed, 3 Aug 2022 11:54:52 +0300 Subject: [PATCH] Introduce configurable refresh support in SmartContextLoader SPI Prior to this commit, the contract of the loadContext() method in the SmartContextLoader SPI required that the ApplicationContext be returned in a fully refreshed state with a JVM shutdown hook registered for it. However, in order to support AOT processing within the Spring TestContext Framework (TCF), we need a way to signal to SmartContextLoader implementations that they should load the test's ApplicationContext without refreshing it or registering a JVM shutdown hook. To address this issue, this commit: - Introduces a new loadContext(MergedContextConfiguration, boolean) method. The boolean `refresh` flag controls whether the returned ApplicationContext should be refreshed and have a JVM shutdown hook registered for it. - Deprecates the existing loadContext(MergedContextConfiguration) method in favor of loadContext(MergedContextConfiguration, boolean). - Removes all use of the deprecated method within the spring-test module, excluding the exception mentioned below. Note that loadContext(MergedContextConfiguration, boolean) is implemented as an interface `default` method which delegates to the deprecated loadContext(MergedContextConfiguration) method for backward compatibility. When migrating a SmartContextLoader to Spring Framework 6.0, implementations that directly implement the SmartContextLoader SPI (instead of extending AbstractGenericContextLoader or AbstractGenericWebContextLoader) will need to override the new loadContext(MergedContextConfiguration, boolean) method in order to honor the `refresh` flag for AOT processing support. See the implementation in AbstractGenericContextLoader for an example of how this can be achieved. Closes gh-28906 --- .../context/MergedContextConfiguration.java | 2 +- .../test/context/SmartContextLoader.java | 90 +++++- ...efaultCacheAwareContextLoaderDelegate.java | 2 +- .../support/AbstractContextLoader.java | 2 +- .../AbstractDelegatingSmartContextLoader.java | 27 +- .../support/AbstractGenericContextLoader.java | 57 ++-- .../web/AbstractGenericWebContextLoader.java | 31 +- .../web/WebMergedContextConfiguration.java | 4 +- .../AnnotatedFooConfigInnerClassTestCase.java | 6 +- .../AnnotationConfigContextLoaderTests.java | 45 ++- ...ustomizedGenericXmlContextLoaderTests.java | 2 +- .../DelegatingSmartContextLoaderTests.java | 282 ++++++++++-------- .../support/GenericXmlContextLoaderTests.java | 4 +- ...AnnotationConfigWebContextLoaderTests.java | 4 +- .../web/GenericXmlWebContextLoaderTests.java | 4 +- 15 files changed, 373 insertions(+), 189 deletions(-) diff --git a/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java index 97841c8c80..5d775e664e 100644 --- a/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java +++ b/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java @@ -62,7 +62,7 @@ import org.springframework.util.StringUtils; * @see ActiveProfiles * @see TestPropertySource * @see ContextConfigurationAttributes - * @see SmartContextLoader#loadContext(MergedContextConfiguration) + * @see SmartContextLoader#loadContext(MergedContextConfiguration, boolean) */ public class MergedContextConfiguration implements Serializable { diff --git a/spring-test/src/main/java/org/springframework/test/context/SmartContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/SmartContextLoader.java index 6faf0c5105..dd9474fc1c 100644 --- a/spring-test/src/main/java/org/springframework/test/context/SmartContextLoader.java +++ b/spring-test/src/main/java/org/springframework/test/context/SmartContextLoader.java @@ -25,34 +25,34 @@ import org.springframework.lang.Nullable; * *

The {@code SmartContextLoader} SPI supersedes the {@link ContextLoader} SPI * introduced in Spring 2.5: a {@code SmartContextLoader} can choose to process - * resource locations, annotated classes, or a combination of both. Furthermore, a + * resource locations, component classes, or a combination of both. Furthermore, a * {@code SmartContextLoader} can configure the context that it - * {@linkplain #loadContext(MergedContextConfiguration) loads} based on any - * properties available in the provided {@link MergedContextConfiguration}. For - * example, active bean definition profiles can be configured for the context + * {@linkplain #loadContext(MergedContextConfiguration, boolean) loads} based on + * any properties available in the provided {@link MergedContextConfiguration}. + * For example, active bean definition profiles can be configured for the context * based on {@link MergedContextConfiguration#getActiveProfiles()}. * *

See the Javadoc for {@link ContextConfiguration @ContextConfiguration} - * for a definition of annotated class. + * for a definition of component classes. * *

Clients of a {@code SmartContextLoader} should call * {@link #processContextConfiguration(ContextConfigurationAttributes) * processContextConfiguration()} prior to calling - * {@link #loadContext(MergedContextConfiguration) loadContext()}. This gives a - * {@code SmartContextLoader} the opportunity to provide custom support for - * modifying resource locations or detecting default resource locations or + * {@link #loadContext(MergedContextConfiguration, boolean) loadContext()}. This + * gives a {@code SmartContextLoader} the opportunity to provide custom support + * for modifying resource locations or detecting default resource locations or * default configuration classes. The results of * {@link #processContextConfiguration(ContextConfigurationAttributes) * processContextConfiguration()} should be merged for all classes in the * hierarchy of the root test class and then supplied to - * {@link #loadContext(MergedContextConfiguration) loadContext()}. + * {@link #loadContext(MergedContextConfiguration, boolean) loadContext()}. * *

NOTE: As of Spring Framework 6.0, {@code SmartContextLoader} no longer * supports methods defined in the {@code ContextLoader} SPI. * *

Concrete implementations must provide a {@code public} no-args constructor. * - *

Spring provides the following out-of-the-box implementations: + *

Spring provides the following {@code SmartContextLoader} implementations. *

* @param mergedConfig the merged context configuration to use to load the application context + * @param refresh whether to refresh the {@code ApplicationContext} and register + * a JVM shutdown hook for it + * @return a new application context * @throws IllegalArgumentException if the supplied merged configuration is {@code null} * @throws IllegalStateException if neither candidate loader is capable of loading an * {@code ApplicationContext} from the supplied merged context configuration + * @since 6.0 */ @Override - public ApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception { + public ApplicationContext loadContext(MergedContextConfiguration mergedConfig, boolean refresh) throws Exception { Assert.notNull(mergedConfig, "MergedContextConfiguration must not be null"); Assert.state(!(mergedConfig.hasLocations() && mergedConfig.hasClasses()), () -> String.format( @@ -209,7 +225,7 @@ public abstract class AbstractDelegatingSmartContextLoader implements SmartConte // Determine if each loader can load a context from the mergedConfig. If it // can, let it; otherwise, keep iterating. if (supports(loader, mergedConfig)) { - return delegateLoading(loader, mergedConfig); + return delegateLoading(loader, mergedConfig, refresh); } } @@ -217,7 +233,7 @@ public abstract class AbstractDelegatingSmartContextLoader implements SmartConte // ACIs or customizers were declared, then delegate to the annotation config // loader. if (!mergedConfig.getContextInitializerClasses().isEmpty() || !mergedConfig.getContextCustomizers().isEmpty()) { - return delegateLoading(getAnnotationConfigLoader(), mergedConfig); + return delegateLoading(getAnnotationConfigLoader(), mergedConfig, refresh); } // else... @@ -235,13 +251,14 @@ public abstract class AbstractDelegatingSmartContextLoader implements SmartConte loader.processContextConfiguration(configAttributes); } - private static ApplicationContext delegateLoading(SmartContextLoader loader, MergedContextConfiguration mergedConfig) + private static ApplicationContext delegateLoading( + SmartContextLoader loader, MergedContextConfiguration mergedConfig, boolean refresh) throws Exception { if (logger.isDebugEnabled()) { logger.debug(String.format("Delegating to %s to load context from %s.", name(loader), mergedConfig)); } - return loader.loadContext(mergedConfig); + return loader.loadContext(mergedConfig, refresh); } private boolean supports(SmartContextLoader loader, MergedContextConfiguration mergedConfig) { diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java index 38d3130cde..c3843014a3 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -40,9 +40,9 @@ import org.springframework.util.StringUtils; *
  • If instances of concrete subclasses are invoked via the * {@link org.springframework.test.context.SmartContextLoader SmartContextLoader} * SPI, the context will be loaded from the {@link MergedContextConfiguration} - * provided to {@link #loadContext(MergedContextConfiguration)}. In such cases, a - * {@code SmartContextLoader} will decide whether to load the context from - * locations or annotated classes.
  • + * provided to {@link #loadContext(MergedContextConfiguration, boolean)}. In such + * cases, a {@code SmartContextLoader} will decide whether to load the context + * from locations or annotated classes. * * *

    Concrete subclasses must provide an appropriate implementation of @@ -54,13 +54,23 @@ import org.springframework.util.StringUtils; * @author Juergen Hoeller * @author Phillip Webb * @since 2.5 - * @see #loadContext(MergedContextConfiguration) - * @see #loadContext(String...) + * @see #loadContext(MergedContextConfiguration, boolean) */ public abstract class AbstractGenericContextLoader extends AbstractContextLoader { protected static final Log logger = LogFactory.getLog(AbstractGenericContextLoader.class); + /** + * Although this method is officially deprecated, for backward compatibility + * it delegates to {@link #loadContext(MergedContextConfiguration, boolean)}, + * supplying {@code true} for the {@code refresh} flag. + * @deprecated as of Spring Framework 6.0, in favor of {@link #loadContext(MergedContextConfiguration, boolean)} + */ + @Override + @Deprecated + public final ApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception { + return loadContext(mergedConfig, true); + } /** * Load a Spring ApplicationContext from the supplied {@link MergedContextConfiguration}. @@ -94,15 +104,22 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader *

  • Calls {@link #customizeContext(ConfigurableApplicationContext, MergedContextConfiguration)} to * allow for customizing the context before it is refreshed.
  • *
  • {@link ConfigurableApplicationContext#refresh Refreshes} the - * context and registers a JVM shutdown hook for it.
  • + * context and registers a JVM shutdown hook for it if the supplied the + * {@code refresh} flag is {@code true}. * + * @param mergedConfig the merged context configuration to use to load the + * application context + * @param refresh whether to refresh the {@code ApplicationContext} and register + * a JVM shutdown hook for it * @return a new application context - * @since 3.1 - * @see org.springframework.test.context.SmartContextLoader#loadContext(MergedContextConfiguration) + * @since 6.0 + * @see org.springframework.test.context.SmartContextLoader#loadContext(MergedContextConfiguration, boolean) * @see GenericApplicationContext */ @Override - public final ConfigurableApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception { + public final ConfigurableApplicationContext loadContext( + MergedContextConfiguration mergedConfig, boolean refresh) throws Exception { + if (logger.isDebugEnabled()) { logger.debug(String.format("Loading ApplicationContext for merged context configuration [%s].", mergedConfig)); @@ -124,8 +141,10 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader customizeContext(context); customizeContext(context, mergedConfig); - context.refresh(); - context.registerShutdownHook(); + if (refresh) { + context.refresh(); + context.registerShutdownHook(); + } return context; } @@ -166,15 +185,15 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader * context and registers a JVM shutdown hook for it. * *

    Note: this method does not provide a means to set active bean definition - * profiles for the loaded context. See {@link #loadContext(MergedContextConfiguration)} + * profiles for the loaded context. See {@link #loadContext(MergedContextConfiguration, boolean)} * and {@link AbstractContextLoader#prepareContext(ConfigurableApplicationContext, MergedContextConfiguration)} * for an alternative. * @return a new application context * @since 2.5 * @see org.springframework.test.context.ContextLoader#loadContext * @see GenericApplicationContext - * @see #loadContext(MergedContextConfiguration) - * @deprecated as of Spring Framework 6.0, in favor of {@link #loadContext(MergedContextConfiguration)} + * @see #loadContext(MergedContextConfiguration, boolean) + * @deprecated as of Spring Framework 6.0, in favor of {@link #loadContext(MergedContextConfiguration, boolean)} */ @Deprecated @Override @@ -219,7 +238,7 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader * customize {@code GenericApplicationContext}'s standard settings. * @param context the context that should be prepared * @since 2.5 - * @see #loadContext(MergedContextConfiguration) + * @see #loadContext(MergedContextConfiguration, boolean) * @see #loadContext(String...) * @see GenericApplicationContext#setAllowBeanDefinitionOverriding * @see GenericApplicationContext#setResourceLoader @@ -236,7 +255,7 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader * to customize {@code DefaultListableBeanFactory}'s standard settings. * @param beanFactory the bean factory created by this {@code ContextLoader} * @since 2.5 - * @see #loadContext(MergedContextConfiguration) + * @see #loadContext(MergedContextConfiguration, boolean) * @see #loadContext(String...) * @see DefaultListableBeanFactory#setAllowBeanDefinitionOverriding * @see DefaultListableBeanFactory#setAllowEagerClassLoading @@ -261,7 +280,7 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader * @param context the context into which the bean definitions should be loaded * @param mergedConfig the merged context configuration * @since 3.1 - * @see #loadContext(MergedContextConfiguration) + * @see #loadContext(MergedContextConfiguration, boolean) */ protected void loadBeanDefinitions(GenericApplicationContext context, MergedContextConfiguration mergedConfig) { createBeanDefinitionReader(context).loadBeanDefinitions(mergedConfig.getLocations()); @@ -288,7 +307,7 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader * to customize the application context. * @param context the newly created application context * @since 2.5 - * @see #loadContext(MergedContextConfiguration) + * @see #loadContext(MergedContextConfiguration, boolean) * @see #loadContext(String...) * @see #customizeContext(ConfigurableApplicationContext, MergedContextConfiguration) */ diff --git a/spring-test/src/main/java/org/springframework/test/context/web/AbstractGenericWebContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/web/AbstractGenericWebContextLoader.java index 8260325057..3fb70624c4 100644 --- a/spring-test/src/main/java/org/springframework/test/context/web/AbstractGenericWebContextLoader.java +++ b/spring-test/src/main/java/org/springframework/test/context/web/AbstractGenericWebContextLoader.java @@ -64,6 +64,18 @@ public abstract class AbstractGenericWebContextLoader extends AbstractContextLoa // SmartContextLoader + /** + * Although this method is officially deprecated, for backward compatibility + * it delegates to {@link #loadContext(MergedContextConfiguration, boolean)}, + * supplying {@code true} for the {@code refresh} flag. + * @deprecated as of Spring Framework 6.0, in favor of {@link #loadContext(MergedContextConfiguration, boolean)} + */ + @Override + @Deprecated + public final ApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception { + return loadContext(mergedConfig, true); + } + /** * Load a Spring {@link WebApplicationContext} from the supplied * {@link MergedContextConfiguration}. @@ -94,12 +106,19 @@ public abstract class AbstractGenericWebContextLoader extends AbstractContextLoa *

  • {@link ConfigurableApplicationContext#refresh Refreshes} the * context and registers a JVM shutdown hook for it.
  • * + * @param mergedConfig the merged context configuration to use to load the + * application context + * @param refresh whether to refresh the {@code ApplicationContext} and register + * a JVM shutdown hook for it * @return a new web application context - * @see org.springframework.test.context.SmartContextLoader#loadContext(MergedContextConfiguration) + * @since 6.0 + * @see org.springframework.test.context.SmartContextLoader#loadContext(MergedContextConfiguration, boolean) * @see GenericWebApplicationContext */ @Override - public final ConfigurableApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception { + public final ConfigurableApplicationContext loadContext( + MergedContextConfiguration mergedConfig, boolean refresh) throws Exception { + Assert.isTrue(mergedConfig instanceof WebMergedContextConfiguration, () -> String.format("Cannot load WebApplicationContext from non-web merged context configuration %s. " + "Consider annotating your test class with @WebAppConfiguration.", mergedConfig)); @@ -125,8 +144,12 @@ public abstract class AbstractGenericWebContextLoader extends AbstractContextLoa loadBeanDefinitions(context, webMergedConfig); AnnotationConfigUtils.registerAnnotationConfigProcessors(context); customizeContext(context, webMergedConfig); - context.refresh(); - context.registerShutdownHook(); + + if (refresh) { + context.refresh(); + context.registerShutdownHook(); + } + return context; } diff --git a/spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java index c0d2819afa..35eece33fb 100644 --- a/spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java +++ b/spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -50,7 +50,7 @@ import org.springframework.util.StringUtils; * @see org.springframework.test.context.ContextConfiguration * @see org.springframework.test.context.ActiveProfiles * @see org.springframework.test.context.ContextConfigurationAttributes - * @see org.springframework.test.context.SmartContextLoader#loadContext(MergedContextConfiguration) + * @see org.springframework.test.context.SmartContextLoader#loadContext(MergedContextConfiguration, boolean) */ public class WebMergedContextConfiguration extends MergedContextConfiguration { diff --git a/spring-test/src/test/java/org/springframework/test/context/support/AnnotatedFooConfigInnerClassTestCase.java b/spring-test/src/test/java/org/springframework/test/context/support/AnnotatedFooConfigInnerClassTestCase.java index 21dec677f0..f5150b7c81 100644 --- a/spring-test/src/test/java/org/springframework/test/context/support/AnnotatedFooConfigInnerClassTestCase.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/AnnotatedFooConfigInnerClassTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2022 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,11 +28,11 @@ import org.springframework.context.annotation.Configuration; */ public class AnnotatedFooConfigInnerClassTestCase { - @Configuration + @Configuration(proxyBeanMethods = false) static class FooConfig { @Bean - public String foo() { + String foo() { return "foo"; } } diff --git a/spring-test/src/test/java/org/springframework/test/context/support/AnnotationConfigContextLoaderTests.java b/spring-test/src/test/java/org/springframework/test/context/support/AnnotationConfigContextLoaderTests.java index aaac4af95e..5262d466a8 100644 --- a/spring-test/src/test/java/org/springframework/test/context/support/AnnotationConfigContextLoaderTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/AnnotationConfigContextLoaderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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,8 +16,11 @@ package org.springframework.test.context.support; +import java.util.Arrays; + import org.junit.jupiter.api.Test; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.test.context.MergedContextConfiguration; import static org.assertj.core.api.Assertions.assertThat; @@ -31,24 +34,54 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException; */ class AnnotationConfigContextLoaderTests { - private final AnnotationConfigContextLoader contextLoader = new AnnotationConfigContextLoader(); - private static final String[] EMPTY_STRING_ARRAY = new String[0]; private static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; + private final AnnotationConfigContextLoader contextLoader = new AnnotationConfigContextLoader(); + /** * @since 4.0.4 */ @Test - void configMustNotContainLocations() throws Exception { + void loadContextWithConfigContainingLocationsResultsInException() { MergedContextConfiguration mergedConfig = new MergedContextConfiguration(getClass(), new String[] { "config.xml" }, EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, contextLoader); - assertThatIllegalStateException().isThrownBy(() -> - contextLoader.loadContext(mergedConfig)) + assertThatIllegalStateException() + .isThrownBy(() -> contextLoader.loadContext(mergedConfig, true)) .withMessageContaining("does not support resource locations"); } + /** + * @since 6.0 + */ + @Test + void loadContextHonorsRefreshTrue() throws Exception { + MergedContextConfiguration mergedConfig = new MergedContextConfiguration( + AnnotatedFooConfigInnerClassTestCase.class, EMPTY_STRING_ARRAY, + new Class[] {AnnotatedFooConfigInnerClassTestCase.FooConfig.class}, + EMPTY_STRING_ARRAY, contextLoader); + ConfigurableApplicationContext context = contextLoader.loadContext(mergedConfig, true); + assertThat(context).isNotNull(); + assertThat(context.isActive()).as("ApplicationContext is active").isTrue(); + assertThat(context.getBean(String.class)).isEqualTo("foo"); + } + + /** + * @since 6.0 + */ + @Test + void loadContextHonorsRefreshFalse() throws Exception { + MergedContextConfiguration mergedConfig = new MergedContextConfiguration( + AnnotatedFooConfigInnerClassTestCase.class, EMPTY_STRING_ARRAY, + new Class[] {AnnotatedFooConfigInnerClassTestCase.FooConfig.class}, + EMPTY_STRING_ARRAY, contextLoader); + ConfigurableApplicationContext context = contextLoader.loadContext(mergedConfig, false); + assertThat(context).isNotNull(); + assertThat(context.isActive()).as("ApplicationContext is active").isFalse(); + assertThat(Arrays.stream(context.getBeanDefinitionNames())).anyMatch(name -> name.contains("FooConfig")); + } + @Test void detectDefaultConfigurationClassesForAnnotatedInnerClass() { Class[] configClasses = contextLoader.detectDefaultConfigurationClasses(ContextConfigurationInnerClassTestCase.class); diff --git a/spring-test/src/test/java/org/springframework/test/context/support/CustomizedGenericXmlContextLoaderTests.java b/spring-test/src/test/java/org/springframework/test/context/support/CustomizedGenericXmlContextLoaderTests.java index 6efe5fc183..d929af7c67 100644 --- a/spring-test/src/test/java/org/springframework/test/context/support/CustomizedGenericXmlContextLoaderTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/CustomizedGenericXmlContextLoaderTests.java @@ -53,7 +53,7 @@ class CustomizedGenericXmlContextLoaderTests { MergedContextConfiguration mergedConfig = new MergedContextConfiguration(getClass(), null, null, null, null); - customLoader.loadContext(mergedConfig); + customLoader.loadContext(mergedConfig, true); assertThat(customizeInvoked).as("customizeContext() should have been invoked").isTrue(); } diff --git a/spring-test/src/test/java/org/springframework/test/context/support/DelegatingSmartContextLoaderTests.java b/spring-test/src/test/java/org/springframework/test/context/support/DelegatingSmartContextLoaderTests.java index 48ad2792a6..de0550b80f 100644 --- a/spring-test/src/test/java/org/springframework/test/context/support/DelegatingSmartContextLoaderTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/DelegatingSmartContextLoaderTests.java @@ -16,6 +16,9 @@ package org.springframework.test.context.support; +import java.util.Arrays; + +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.context.ApplicationContext; @@ -25,7 +28,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.test.context.ContextConfigurationAttributes; import org.springframework.test.context.ContextLoader; import org.springframework.test.context.MergedContextConfiguration; -import org.springframework.util.ObjectUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -45,135 +47,167 @@ class DelegatingSmartContextLoaderTests { private final DelegatingSmartContextLoader loader = new DelegatingSmartContextLoader(); + @Nested + class SmartContextLoaderSpiTests { + + @Test + void processContextConfigurationWithDefaultXmlConfigGeneration() { + ContextConfigurationAttributes configAttributes = new ContextConfigurationAttributes( + XmlTestCase.class, EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, true, null, true, ContextLoader.class); + loader.processContextConfiguration(configAttributes); + assertThat(configAttributes.getLocations()).hasSize(1); + assertThat(configAttributes.getClasses()).isEmpty(); + } + + @Test + void processContextConfigurationWithDefaultConfigurationClassGeneration() { + ContextConfigurationAttributes configAttributes = new ContextConfigurationAttributes( + ConfigClassTestCase.class, EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, true, null, true, ContextLoader.class); + loader.processContextConfiguration(configAttributes); + assertThat(configAttributes.getClasses()).hasSize(1); + assertThat(configAttributes.getLocations()).isEmpty(); + } + + @Test + void processContextConfigurationWithDefaultXmlConfigAndConfigurationClassGeneration() { + ContextConfigurationAttributes configAttributes = new ContextConfigurationAttributes( + ImproperDuplicateDefaultXmlAndConfigClassTestCase.class, EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, + true, null, true, ContextLoader.class); + assertThatIllegalStateException() + .isThrownBy(() -> loader.processContextConfiguration(configAttributes)) + .withMessageContaining("both default locations AND default configuration classes were detected"); + } + + @Test + void processContextConfigurationWithLocation() { + String[] locations = new String[] {"classpath:/foo.xml"}; + ContextConfigurationAttributes configAttributes = new ContextConfigurationAttributes( + getClass(), locations, EMPTY_CLASS_ARRAY, true, null, true, ContextLoader.class); + loader.processContextConfiguration(configAttributes); + assertThat(configAttributes.getLocations()).isEqualTo(locations); + assertThat(configAttributes.getClasses()).isEmpty(); + } + + @Test + void processContextConfigurationWithConfigurationClass() { + Class[] classes = new Class[] {getClass()}; + ContextConfigurationAttributes configAttributes = new ContextConfigurationAttributes( + getClass(), EMPTY_STRING_ARRAY, classes, true, null, true, ContextLoader.class); + loader.processContextConfiguration(configAttributes); + assertThat(configAttributes.getClasses()).isEqualTo(classes); + assertThat(configAttributes.getLocations()).isEmpty(); + } + + @Test + void loadContextWithNullConfig() throws Exception { + assertThatIllegalArgumentException().isThrownBy(() -> loader.loadContext(null, true)); + } + + @Test + void loadContextWithoutLocationsAndConfigurationClasses() throws Exception { + MergedContextConfiguration mergedConfig = new MergedContextConfiguration( + getClass(), EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader); + assertThatIllegalStateException() + .isThrownBy(() -> loader.loadContext(mergedConfig, true)) + .withMessageStartingWith("Neither") + .withMessageContaining("was able to load an ApplicationContext from"); + } + + /** + * @since 4.1 + */ + @Test + void loadContextWithLocationsAndConfigurationClasses() throws Exception { + MergedContextConfiguration mergedConfig = new MergedContextConfiguration(getClass(), + new String[] {"test.xml"}, new Class[] {getClass()}, EMPTY_STRING_ARRAY, loader); + assertThatIllegalStateException() + .isThrownBy(() -> loader.loadContext(mergedConfig, true)) + .withMessageStartingWith("Neither") + .withMessageContaining("declare either 'locations' or 'classes' but not both."); + } + + @Test + void loadContextWithXmlConfig() throws Exception { + MergedContextConfiguration mergedConfig = new MergedContextConfiguration( + XmlTestCase.class, + new String[] {"classpath:/org/springframework/test/context/support/DelegatingSmartContextLoaderTests$XmlTestCase-context.xml"}, + EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader); + + assertApplicationContextLoadsAndContainsFooString(mergedConfig); + } + + @Test + void loadContextWithConfigurationClass() throws Exception { + MergedContextConfiguration mergedConfig = new MergedContextConfiguration(ConfigClassTestCase.class, + EMPTY_STRING_ARRAY, new Class[] {ConfigClassTestCase.Config.class}, EMPTY_STRING_ARRAY, loader); + + assertApplicationContextLoadsAndContainsFooString(mergedConfig); + } + + private void assertApplicationContextLoadsAndContainsFooString(MergedContextConfiguration mergedConfig) + throws Exception { + + ApplicationContext applicationContext = loader.loadContext(mergedConfig, true); + assertThat(applicationContext).isInstanceOf(ConfigurableApplicationContext.class); + assertThat(applicationContext.getBean(String.class)).isEqualTo("foo"); + ConfigurableApplicationContext cac = (ConfigurableApplicationContext) applicationContext; + cac.close(); + } + + /** + * @since 6.0 + */ + @Test + void loadContextWithXmlConfigWithoutRefresh() throws Exception { + MergedContextConfiguration mergedConfig = new MergedContextConfiguration( + XmlTestCase.class, + new String[] {"classpath:/org/springframework/test/context/support/DelegatingSmartContextLoaderTests$XmlTestCase-context.xml"}, + EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader); + + assertApplicationContextLoadsWithoutRefresh(mergedConfig, "foo"); + } + + /** + * @since 6.0 + */ + @Test + void loadContextWithConfigurationClassWithoutRefresh() throws Exception { + MergedContextConfiguration mergedConfig = new MergedContextConfiguration(ConfigClassTestCase.class, + EMPTY_STRING_ARRAY, new Class[] {ConfigClassTestCase.Config.class}, EMPTY_STRING_ARRAY, loader); + + assertApplicationContextLoadsWithoutRefresh(mergedConfig, "ConfigClassTestCase.Config"); + } + + private void assertApplicationContextLoadsWithoutRefresh(MergedContextConfiguration mergedConfig, + String expectedBeanDefName) throws Exception { + + ApplicationContext context = loader.loadContext(mergedConfig, false); + assertThat(context).isInstanceOf(ConfigurableApplicationContext.class); + ConfigurableApplicationContext cac = (ConfigurableApplicationContext) context; + assertThat(cac.isActive()).as("ApplicationContext is active").isFalse(); + assertThat(Arrays.stream(context.getBeanDefinitionNames())).anyMatch(name -> name.contains(expectedBeanDefName)); + cac.close(); + } - private static void assertEmpty(Object[] array) { - assertThat(ObjectUtils.isEmpty(array)).isTrue(); } - // --- SmartContextLoader - processContextConfiguration() ------------------ + @Nested + class ContextLoaderSpiTests { + + @Test + void processLocations() { + assertThatExceptionOfType(UnsupportedOperationException.class) + .isThrownBy(() -> loader.processLocations(getClass(), EMPTY_STRING_ARRAY)); + } + + @Test + void loadContextFromLocations() { + assertThatExceptionOfType(UnsupportedOperationException.class) + .isThrownBy(() -> loader.loadContext(EMPTY_STRING_ARRAY)); + } - @Test - void processContextConfigurationWithDefaultXmlConfigGeneration() { - ContextConfigurationAttributes configAttributes = new ContextConfigurationAttributes( - XmlTestCase.class, EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, true, null, true, ContextLoader.class); - loader.processContextConfiguration(configAttributes); - assertThat(configAttributes.getLocations().length).isEqualTo(1); - assertEmpty(configAttributes.getClasses()); } - @Test - void processContextConfigurationWithDefaultConfigurationClassGeneration() { - ContextConfigurationAttributes configAttributes = new ContextConfigurationAttributes( - ConfigClassTestCase.class, EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, true, null, true, ContextLoader.class); - loader.processContextConfiguration(configAttributes); - assertThat(configAttributes.getClasses().length).isEqualTo(1); - assertEmpty(configAttributes.getLocations()); - } - - @Test - void processContextConfigurationWithDefaultXmlConfigAndConfigurationClassGeneration() { - ContextConfigurationAttributes configAttributes = new ContextConfigurationAttributes( - ImproperDuplicateDefaultXmlAndConfigClassTestCase.class, EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, - true, null, true, ContextLoader.class); - assertThatIllegalStateException().isThrownBy(() -> - loader.processContextConfiguration(configAttributes)) - .withMessageContaining("both default locations AND default configuration classes were detected"); - } - - @Test - void processContextConfigurationWithLocation() { - String[] locations = new String[] {"classpath:/foo.xml"}; - ContextConfigurationAttributes configAttributes = new ContextConfigurationAttributes( - getClass(), locations, EMPTY_CLASS_ARRAY, true, null, true, ContextLoader.class); - loader.processContextConfiguration(configAttributes); - assertThat(configAttributes.getLocations()).isEqualTo(locations); - assertEmpty(configAttributes.getClasses()); - } - - @Test - void processContextConfigurationWithConfigurationClass() { - Class[] classes = new Class[] {getClass()}; - ContextConfigurationAttributes configAttributes = new ContextConfigurationAttributes( - getClass(), EMPTY_STRING_ARRAY, classes, true, null, true, ContextLoader.class); - loader.processContextConfiguration(configAttributes); - assertThat(configAttributes.getClasses()).isEqualTo(classes); - assertEmpty(configAttributes.getLocations()); - } - - // --- SmartContextLoader - loadContext() ---------------------------------- - - @Test - void loadContextWithNullConfig() throws Exception { - assertThatIllegalArgumentException().isThrownBy(() -> - loader.loadContext((MergedContextConfiguration) null)); - } - - @Test - void loadContextWithoutLocationsAndConfigurationClasses() throws Exception { - MergedContextConfiguration mergedConfig = new MergedContextConfiguration( - getClass(), EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader); - assertThatIllegalStateException().isThrownBy(() -> - loader.loadContext(mergedConfig)) - .withMessageStartingWith("Neither") - .withMessageContaining("was able to load an ApplicationContext from"); - } - - /** - * @since 4.1 - */ - @Test - void loadContextWithLocationsAndConfigurationClasses() throws Exception { - MergedContextConfiguration mergedConfig = new MergedContextConfiguration(getClass(), - new String[] {"test.xml"}, new Class[] {getClass()}, EMPTY_STRING_ARRAY, loader); - assertThatIllegalStateException().isThrownBy(() -> - loader.loadContext(mergedConfig)) - .withMessageStartingWith("Neither") - .withMessageContaining("declare either 'locations' or 'classes' but not both."); - } - - private void assertApplicationContextLoadsAndContainsFooString(MergedContextConfiguration mergedConfig) - throws Exception { - - ApplicationContext applicationContext = loader.loadContext(mergedConfig); - assertThat(applicationContext).isNotNull(); - assertThat(applicationContext.getBean(String.class)).isEqualTo("foo"); - boolean condition = applicationContext instanceof ConfigurableApplicationContext; - assertThat(condition).isTrue(); - ((ConfigurableApplicationContext) applicationContext).close(); - } - - @Test - void loadContextWithXmlConfig() throws Exception { - MergedContextConfiguration mergedConfig = new MergedContextConfiguration( - XmlTestCase.class, - new String[] {"classpath:/org/springframework/test/context/support/DelegatingSmartContextLoaderTests$XmlTestCase-context.xml"}, - EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader); - assertApplicationContextLoadsAndContainsFooString(mergedConfig); - } - - @Test - void loadContextWithConfigurationClass() throws Exception { - MergedContextConfiguration mergedConfig = new MergedContextConfiguration(ConfigClassTestCase.class, - EMPTY_STRING_ARRAY, new Class[] {ConfigClassTestCase.Config.class}, EMPTY_STRING_ARRAY, loader); - assertApplicationContextLoadsAndContainsFooString(mergedConfig); - } - - // --- ContextLoader ------------------------------------------------------- - - @Test - void processLocations() { - assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> - loader.processLocations(getClass(), EMPTY_STRING_ARRAY)); - } - - @Test - void loadContextFromLocations() { - assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> - loader.loadContext(EMPTY_STRING_ARRAY)); - } - - - // ------------------------------------------------------------------------- static class XmlTestCase { } diff --git a/spring-test/src/test/java/org/springframework/test/context/support/GenericXmlContextLoaderTests.java b/spring-test/src/test/java/org/springframework/test/context/support/GenericXmlContextLoaderTests.java index 0b3960fd5c..33755b80d2 100644 --- a/spring-test/src/test/java/org/springframework/test/context/support/GenericXmlContextLoaderTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/GenericXmlContextLoaderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -40,7 +40,7 @@ class GenericXmlContextLoaderTests { MergedContextConfiguration mergedConfig = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY, new Class[] { getClass() }, EMPTY_STRING_ARRAY, loader); assertThatIllegalStateException() - .isThrownBy(() -> loader.loadContext(mergedConfig)) + .isThrownBy(() -> loader.loadContext(mergedConfig, true)) .withMessageContaining("does not support annotated classes"); } diff --git a/spring-test/src/test/java/org/springframework/test/context/web/AnnotationConfigWebContextLoaderTests.java b/spring-test/src/test/java/org/springframework/test/context/web/AnnotationConfigWebContextLoaderTests.java index 221f84c29e..f5921a8c43 100644 --- a/spring-test/src/test/java/org/springframework/test/context/web/AnnotationConfigWebContextLoaderTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/web/AnnotationConfigWebContextLoaderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -39,7 +39,7 @@ class AnnotationConfigWebContextLoaderTests { new String[] { "config.xml" }, EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, EMPTY_STRING_ARRAY, EMPTY_STRING_ARRAY, "resource/path", loader, null, null); assertThatIllegalStateException() - .isThrownBy(() -> loader.loadContext(mergedConfig)) + .isThrownBy(() -> loader.loadContext(mergedConfig, true)) .withMessageContaining("does not support resource locations"); } diff --git a/spring-test/src/test/java/org/springframework/test/context/web/GenericXmlWebContextLoaderTests.java b/spring-test/src/test/java/org/springframework/test/context/web/GenericXmlWebContextLoaderTests.java index e6fc82e65b..17bb1034a2 100644 --- a/spring-test/src/test/java/org/springframework/test/context/web/GenericXmlWebContextLoaderTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/web/GenericXmlWebContextLoaderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -38,7 +38,7 @@ class GenericXmlWebContextLoaderTests { new Class[] { getClass() }, null, EMPTY_STRING_ARRAY, EMPTY_STRING_ARRAY, EMPTY_STRING_ARRAY, "resource/path", loader, null, null); assertThatIllegalStateException() - .isThrownBy(() -> loader.loadContext(mergedConfig)) + .isThrownBy(() -> loader.loadContext(mergedConfig, true)) .withMessageContaining("does not support annotated classes"); }