Browse Source

Support declarative ContextCustomizerFactory registration in the TCF

Prior to this commit, it was only possible to register a
ContextCustomizerFactory in the TestContext framework (TCF) via the
SpringFactoriesLoader mechanism.

This commit introduces support for declarative registration of a
ContextCustomizerFactory local to a test class via a new
@ContextCustomizerFactories annotation.

Closes gh-26148
pull/31226/head
Sam Brannen 1 year ago
parent
commit
a220c545fc
  1. 2
      framework-docs/modules/ROOT/nav.adoc
  2. 1
      framework-docs/modules/ROOT/pages/testing/annotations/integration-junit-jupiter.adoc
  3. 1
      framework-docs/modules/ROOT/pages/testing/annotations/integration-meta.adoc
  4. 1
      framework-docs/modules/ROOT/pages/testing/annotations/integration-spring.adoc
  5. 45
      framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-contextcustomizerfactories.adoc
  6. 1
      framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management.adoc
  7. 69
      framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/context-customizers.adoc
  8. 1
      spring-test/src/main/java/org/springframework/test/context/ContextCustomizer.java
  9. 125
      spring-test/src/main/java/org/springframework/test/context/ContextCustomizerFactories.java
  10. 10
      spring-test/src/main/java/org/springframework/test/context/ContextCustomizerFactory.java
  11. 93
      spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java
  12. 37
      spring-test/src/test/java/org/springframework/test/context/customizers/BarContextCustomizerFactory.java
  13. 70
      spring-test/src/test/java/org/springframework/test/context/customizers/ContextCustomizerDeclarativeRegistrationComposedAnnotationTests.java
  14. 77
      spring-test/src/test/java/org/springframework/test/context/customizers/ContextCustomizerDeclarativeRegistrationTests.java
  15. 94
      spring-test/src/test/java/org/springframework/test/context/customizers/ContextCustomizerTests.java
  16. 34
      spring-test/src/test/java/org/springframework/test/context/customizers/CustomizeWithBar.java
  17. 34
      spring-test/src/test/java/org/springframework/test/context/customizers/CustomizeWithFoo.java
  18. 35
      spring-test/src/test/java/org/springframework/test/context/customizers/CustomizeWithFruit.java
  19. 37
      spring-test/src/test/java/org/springframework/test/context/customizers/EnigmaContextCustomizerFactory.java
  20. 37
      spring-test/src/test/java/org/springframework/test/context/customizers/FooContextCustomizerFactory.java
  21. 65
      spring-test/src/test/java/org/springframework/test/context/customizers/GlobalFruitContextCustomizerFactory.java
  22. 46
      spring-test/src/test/java/org/springframework/test/context/customizers/InheritedContextCustomizerRegistrationTests.java
  23. 52
      spring-test/src/test/java/org/springframework/test/context/customizers/LocalContextCustomizerRegistrationTests.java
  24. 57
      spring-test/src/test/java/org/springframework/test/context/customizers/OverriddenContextCustomizerRegistrationTests.java
  25. 62
      spring-test/src/test/java/org/springframework/test/context/customizers/ReplaceDefaultsContextCustomizerTests.java
  26. 66
      spring-test/src/test/java/org/springframework/test/context/junit4/ContextCustomizerSpringRunnerTests.java
  27. 3
      spring-test/src/test/resources/META-INF/spring.factories

2
framework-docs/modules/ROOT/nav.adoc

@ -122,6 +122,7 @@ @@ -122,6 +122,7 @@
**** xref:testing/testcontext-framework/ctx-management/groovy.adoc[]
**** xref:testing/testcontext-framework/ctx-management/javaconfig.adoc[]
**** xref:testing/testcontext-framework/ctx-management/mixed-config.adoc[]
**** xref:testing/testcontext-framework/ctx-management/context-customizers.adoc[]
**** xref:testing/testcontext-framework/ctx-management/initializers.adoc[]
**** xref:testing/testcontext-framework/ctx-management/inheritance.adoc[]
**** xref:testing/testcontext-framework/ctx-management/env-profiles.adoc[]
@ -166,6 +167,7 @@ @@ -166,6 +167,7 @@
***** xref:testing/annotations/integration-spring/annotation-contextconfiguration.adoc[]
***** xref:testing/annotations/integration-spring/annotation-webappconfiguration.adoc[]
***** xref:testing/annotations/integration-spring/annotation-contexthierarchy.adoc[]
***** xref:testing/annotations/integration-spring/annotation-contextcustomizerfactories.adoc[]
***** xref:testing/annotations/integration-spring/annotation-activeprofiles.adoc[]
***** xref:testing/annotations/integration-spring/annotation-testpropertysource.adoc[]
***** xref:testing/annotations/integration-spring/annotation-dynamicpropertysource.adoc[]

1
framework-docs/modules/ROOT/pages/testing/annotations/integration-junit-jupiter.adoc

@ -223,6 +223,7 @@ following annotations. @@ -223,6 +223,7 @@ following annotations.
* xref:testing/annotations/integration-spring/annotation-contextconfiguration.adoc[`@ContextConfiguration`]
* xref:testing/annotations/integration-spring/annotation-webappconfiguration.adoc[`@WebAppConfiguration`]
* xref:testing/annotations/integration-spring/annotation-contexthierarchy.adoc[`@ContextHierarchy`]
* xref:testing/annotations/integration-spring/annotation-contextcustomizerfactories.adoc[`@ContextCustomizerFactories`]
* xref:testing/annotations/integration-spring/annotation-activeprofiles.adoc[`@ActiveProfiles`]
* xref:testing/annotations/integration-spring/annotation-testpropertysource.adoc[`@TestPropertySource`]
* xref:testing/annotations/integration-spring/annotation-dynamicpropertysource.adoc[`@DynamicPropertySource`]

1
framework-docs/modules/ROOT/pages/testing/annotations/integration-meta.adoc

@ -11,6 +11,7 @@ xref:testing/testcontext-framework.adoc[TestContext framework]. @@ -11,6 +11,7 @@ xref:testing/testcontext-framework.adoc[TestContext framework].
* `@BootstrapWith`
* `@ContextConfiguration`
* `@ContextHierarchy`
* `@ContextCustomizerFactories`
* `@ActiveProfiles`
* `@TestPropertySource`
* `@DirtiesContext`

1
framework-docs/modules/ROOT/pages/testing/annotations/integration-spring.adoc

@ -12,6 +12,7 @@ Spring's testing annotations include the following: @@ -12,6 +12,7 @@ Spring's testing annotations include the following:
* xref:testing/annotations/integration-spring/annotation-contextconfiguration.adoc[`@ContextConfiguration`]
* xref:testing/annotations/integration-spring/annotation-webappconfiguration.adoc[`@WebAppConfiguration`]
* xref:testing/annotations/integration-spring/annotation-contexthierarchy.adoc[`@ContextHierarchy`]
* xref:testing/annotations/integration-spring/annotation-contextcustomizerfactories.adoc[`@ContextCustomizerFactories`]
* xref:testing/annotations/integration-spring/annotation-activeprofiles.adoc[`@ActiveProfiles`]
* xref:testing/annotations/integration-spring/annotation-testpropertysource.adoc[`@TestPropertySource`]
* xref:testing/annotations/integration-spring/annotation-dynamicpropertysource.adoc[`@DynamicPropertySource`]

45
framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-contextcustomizerfactories.adoc

@ -0,0 +1,45 @@ @@ -0,0 +1,45 @@
[[spring-testing-annotation-contextcustomizerfactories]]
= `@ContextCustomizerFactories`
`@ContextCustomizerFactories` is used to register `ContextCustomizerFactory`
implementations for a particular test class, its subclasses, and its nested classes. If
you wish to register a factory globally, you should register it via the automatic
discovery mechanism described in
xref:testing/testcontext-framework/ctx-management/context-customizers.adoc[`ContextCustomizerFactory` Configuration].
The following example shows how to register two `ContextCustomizerFactory` implementations:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
@ContextConfiguration
@ContextCustomizerFactories({CustomContextCustomizerFactory.class, AnotherContextCustomizerFactory.class}) // <1>
class CustomContextCustomizerFactoryTests {
// class body...
}
----
<1> Register two `ContextCustomizerFactory` implementations.
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
@ContextConfiguration
@ContextCustomizerFactories([CustomContextCustomizerFactory::class, AnotherContextCustomizerFactory::class]) // <1>
class CustomContextCustomizerFactoryTests {
// class body...
}
----
<1> Register two `ContextCustomizerFactory` implementations.
======
By default, `@ContextCustomizerFactories` provides support for inheriting factories from
superclasses or enclosing classes. See
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-nested-test-configuration[`@Nested` test class configuration] and the
{api-spring-framework}/test/context/ContextCustomizerFactories.html[`@ContextCustomizerFactories`
javadoc] for an example and further details.

1
framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management.adoc

@ -112,6 +112,7 @@ advanced use cases. @@ -112,6 +112,7 @@ advanced use cases.
* xref:testing/testcontext-framework/ctx-management/groovy.adoc[Context Configuration with Groovy Scripts]
* xref:testing/testcontext-framework/ctx-management/javaconfig.adoc[Context Configuration with Component Classes]
* xref:testing/testcontext-framework/ctx-management/mixed-config.adoc[Mixing XML, Groovy Scripts, and Component Classes]
* xref:testing/testcontext-framework/ctx-management/context-customizers.adoc[Context Configuration with Context Customizers]
* xref:testing/testcontext-framework/ctx-management/initializers.adoc[Context Configuration with Context Initializers]
* xref:testing/testcontext-framework/ctx-management/inheritance.adoc[Context Configuration Inheritance]
* xref:testing/testcontext-framework/ctx-management/env-profiles.adoc[Context Configuration with Environment Profiles]

69
framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/context-customizers.adoc

@ -0,0 +1,69 @@ @@ -0,0 +1,69 @@
[[testcontext-context-customizers]]
= Configuration Configuration with Context Customizers
A `ContextCustomizer` is responsible for customizing the supplied
`ConfigurableApplicationContext` after bean definitions have been loaded into the context
but before the context has been refreshed.
A `ContextCustomizerFactory` is responsible for creating a `ContextCustomizer`, based on
some custom logic which determines if the `ContextCustomizer` is necessary for a given
test class -- for example, based on the presence of a certain annotation. Factories are
invoked after `ContextLoaders` have processed context configuration attributes for a test
class but before the `MergedContextConfiguration` is created.
For example, Spring Framework provides the following `ContextCustomizerFactory`
implementation which is registered by default:
`MockServerContainerContextCustomizerFactory`:: Creates a
`MockServerContainerContextCustomizer` if WebSocket support is present in the classpath
and the test class or one of its enclosing classes is annotated or meta-annotated with
`@WebAppConfiguration`. `MockServerContainerContextCustomizer` instantiates a new
`MockServerContainer` and stores it in the `ServletContext` under the attribute named
`jakarta.websocket.server.ServerContainer`.
[[testcontext-context-customizers-registration]]
== Registering `ContextCustomizerFactory` Implementations
You can register `ContextCustomizerFactory` implementations explicitly for a test class, its
subclasses, and its nested classes by using the `@ContextCustomizerFactories` annotation. See
xref:testing/annotations/integration-spring/annotation-contextcustomizerfactories.adoc[annotation support]
and the javadoc for
{api-spring-framework}/test/context/ContextCustomizerFactories.html[`@ContextCustomizerFactories`]
for details and examples.
[[testcontext-context-customizers-automatic-discovery]]
== Automatic Discovery of Default `ContextCustomizerFactory` Implementations
Registering `ContextCustomizerFactory` implementations by using `@ContextCustomizerFactories` is
suitable for custom factories that are used in limited testing scenarios. However, it can
become cumbersome if a custom factory needs to be used across an entire test suite. This
issue is addressed through support for automatic discovery of default
`ContextCustomizerFactory` implementations through the `SpringFactoriesLoader` mechanism.
Specifically, the modules that make up the testing support in Spring Framework and Spring
Boot declare all core default `ContextCustomizerFactory` implementations under the
`org.springframework.test.context.ContextCustomizerFactory` key in their
`META-INF/spring.factories` properties files. Third-party frameworks and developers can
contribute their own `ContextCustomizerFactory` implementations to the list of default
factories in the same manner through their own `META-INF/spring.factories` properties
files.
[[testcontext-context-customizers-merging]]
== Merging `ContextCustomizerFactory` Implementations
If a custom `ContextCustomizerFactory` is registered via `@ContextCustomizerFactories`, it
will be _merged_ with the default factories that have been registered using the aforementioned
xref:testing/testcontext-framework/ctx-management/context-customizers.adoc#testcontext-context-customizers-automatic-discovery[automatic discovery mechanism].
The merging algorithm ensures that duplicates are removed from the list and that locally
declared factories are appended to the list of default factories when merged.
[TIP]
====
To replace the default factories for a test class, its subclasses, and its nested
classes, you can set the `mergeMode` attribute of `@ContextCustomizerFactories` to
`MergeMode.REPLACE_DEFAULTS`.
====

1
spring-test/src/main/java/org/springframework/test/context/ContextCustomizer.java

@ -34,6 +34,7 @@ import org.springframework.context.ConfigurableApplicationContext; @@ -34,6 +34,7 @@ import org.springframework.context.ConfigurableApplicationContext;
* @author Sam Brannen
* @since 4.3
* @see ContextCustomizerFactory
* @see ContextCustomizerFactories @ContextCustomizerFactories
* @see org.springframework.test.context.support.AbstractContextLoader#customizeContext
*/
@FunctionalInterface

125
spring-test/src/main/java/org/springframework/test/context/ContextCustomizerFactories.java

@ -0,0 +1,125 @@ @@ -0,0 +1,125 @@
/*
* Copyright 2002-2023 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;
import org.springframework.core.annotation.AliasFor;
/**
* {@code @ContextCustomizerFactories} defines class-level metadata for configuring
* which {@link ContextCustomizerFactory} implementations should be registered with
* the <em>Spring TestContext Framework</em>.
*
* <p>{@code @ContextCustomizerFactories} is used to register factories for a
* particular test class, its subclasses, and its nested classes. If you wish to
* register a factory globally, you should register it via the automatic discovery
* mechanism described in {@link ContextCustomizerFactory}.
*
* <p>This annotation may be used as a <em>meta-annotation</em> to create custom
* <em>composed annotations</em>. In addition, this annotation will be inherited
* from an enclosing test class by default. See
* {@link NestedTestConfiguration @NestedTestConfiguration} for details.
*
* @author Sam Brannen
* @since 6.1
* @see ContextCustomizerFactory
* @see ContextCustomizer
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface ContextCustomizerFactories {
/**
* Alias for {@link #factories}.
* <p>This attribute may <strong>not</strong> be used in conjunction with
* {@link #factories}, but it may be used instead of {@link #factories}.
*/
@AliasFor("factories")
Class<? extends ContextCustomizerFactory>[] value() default {};
/**
* The {@link ContextCustomizerFactory} implementations to register.
* <p>This attribute may <strong>not</strong> be used in conjunction with
* {@link #value}, but it may be used instead of {@link #value}.
*/
@AliasFor("value")
Class<? extends ContextCustomizerFactory>[] factories() default {};
/**
* Whether the configured set of {@link #factories} from superclasses and
* enclosing classes should be <em>inherited</em>.
* <p>The default value is {@code true}, which means that an annotated class
* will <em>inherit</em> the factories defined by an annotated superclass or
* enclosing class. Specifically, the factories for an annotated class will be
* appended to the list of factories defined by an annotated superclass or
* enclosing class. Thus, subclasses and nested classes have the option of
* <em>extending</em> the list of factories.
* <p>If {@code inheritListeners} is set to {@code false}, the factories for
* the annotated class will <em>shadow</em> and effectively replace any
* factories defined by a superclass or enclosing class.
*/
boolean inheritFactories() default true;
/**
* The <em>merge mode</em> to use when {@code @ContextCustomizerFactories} is
* declared on a class that does <strong>not</strong> inherit factories from
* a superclass or enclosing class.
* <p>Can be set to {@link MergeMode#REPLACE_DEFAULTS REPLACE_DEFAULTS} to
* have locally declared factories replace the default factories.
* <p>The mode is ignored if factories are inherited from a superclass or
* enclosing class.
* <p>Defaults to {@link MergeMode#MERGE_WITH_DEFAULTS MERGE_WITH_DEFAULTS}.
* @see MergeMode
*/
MergeMode mergeMode() default MergeMode.MERGE_WITH_DEFAULTS;
/**
* Enumeration of <em>modes</em> that dictate whether explicitly declared
* factories are merged with the default factories when
* {@code @ContextCustomizerFactories} is declared on a class that does
* <strong>not</strong> inherit factories from a superclass or enclosing
* class.
*/
enum MergeMode {
/**
* Indicates that locally declared factories should be merged with the
* default factories.
* <p>The merging algorithm ensures that duplicates are removed from the
* list and that locally declared factories are appended to the list of
* default factories when merged.
*/
MERGE_WITH_DEFAULTS,
/**
* Indicates that locally declared factories should replace the default
* factories.
*/
REPLACE_DEFAULTS
}
}

10
spring-test/src/main/java/org/springframework/test/context/ContextCustomizerFactory.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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,12 +29,18 @@ import org.springframework.lang.Nullable; @@ -29,12 +29,18 @@ import org.springframework.lang.Nullable;
*
* <p>By default, the Spring TestContext Framework will use the
* {@link org.springframework.core.io.support.SpringFactoriesLoader SpringFactoriesLoader}
* mechanism for loading factories configured in all {@code META-INF/spring.factories}
* mechanism for loading default factories configured in all {@code META-INF/spring.factories}
* files on the classpath.
*
* <p>As of Spring Framework 6.1, it is also possible to register factories
* declaratively via the {@link ContextCustomizerFactories @ContextCustomizerFactories}
* annotation.
*
* @author Phillip Webb
* @author Sam Brannen
* @since 4.3
* @see ContextCustomizer
* @see ContextCustomizerFactories @ContextCustomizerFactories
*/
@FunctionalInterface
public interface ContextCustomizerFactory {

93
spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java

@ -37,6 +37,7 @@ import org.springframework.test.context.CacheAwareContextLoaderDelegate; @@ -37,6 +37,7 @@ import org.springframework.test.context.CacheAwareContextLoaderDelegate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactories;
import org.springframework.test.context.ContextCustomizerFactory;
import org.springframework.test.context.ContextHierarchy;
import org.springframework.test.context.ContextLoader;
@ -378,7 +379,7 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot @@ -378,7 +379,7 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
private Set<ContextCustomizer> getContextCustomizers(Class<?> testClass,
List<ContextConfigurationAttributes> configAttributes) {
List<ContextCustomizerFactory> factories = getContextCustomizerFactories();
List<ContextCustomizerFactory> factories = getContextCustomizerFactories(testClass);
Set<ContextCustomizer> customizers = new LinkedHashSet<>(factories.size());
for (ContextCustomizerFactory factory : factories) {
ContextCustomizer customizer = factory.createContextCustomizer(testClass, configAttributes);
@ -397,6 +398,69 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot @@ -397,6 +398,69 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
return customizers;
}
private List<ContextCustomizerFactory> getContextCustomizerFactories(Class<?> testClass) {
AnnotationDescriptor<ContextCustomizerFactories> descriptor =
TestContextAnnotationUtils.findAnnotationDescriptor(testClass, ContextCustomizerFactories.class);
List<ContextCustomizerFactory> factories = new ArrayList<>();
if (descriptor == null) {
if (logger.isTraceEnabled()) {
logger.trace("@ContextCustomizerFactories is not present for class [%s]"
.formatted(testClass.getName()));
}
factories.addAll(getContextCustomizerFactories());
}
else {
// Traverse the class hierarchy...
while (descriptor != null) {
Class<?> declaringClass = descriptor.getDeclaringClass();
ContextCustomizerFactories annotation = descriptor.getAnnotation();
if (logger.isTraceEnabled()) {
logger.trace("Retrieved %s for declaring class [%s]."
.formatted(annotation, declaringClass.getName()));
}
boolean inheritFactories = annotation.inheritFactories();
AnnotationDescriptor<ContextCustomizerFactories> parentDescriptor = descriptor.next();
factories.addAll(0, instantiateCustomizerFactories(annotation.factories()));
// If there are no factories to inherit, we might need to merge the
// locally declared factories with the defaults.
if ((!inheritFactories || parentDescriptor == null) &&
annotation.mergeMode() == ContextCustomizerFactories.MergeMode.MERGE_WITH_DEFAULTS) {
if (logger.isTraceEnabled()) {
logger.trace(String.format("Merging default factories with factories configured via " +
"@ContextCustomizerFactories for class [%s].", descriptor.getRootDeclaringClass().getName()));
}
factories.addAll(0, getContextCustomizerFactories());
}
descriptor = (inheritFactories ? parentDescriptor : null);
}
}
// Remove possible duplicates.
List<ContextCustomizerFactory> uniqueFactories = new ArrayList<>(factories.size());
factories.forEach(factory -> {
Class<? extends ContextCustomizerFactory> factoryClass = factory.getClass();
if (uniqueFactories.stream().map(Object::getClass).noneMatch(factoryClass::equals)) {
uniqueFactories.add(factory);
}
});
factories = uniqueFactories;
if (logger.isTraceEnabled()) {
logger.trace("Using ContextCustomizerFactory implementations for test class [%s]: %s"
.formatted(testClass.getName(), factories));
}
else if (logger.isDebugEnabled()) {
logger.debug("Using ContextCustomizerFactory implementations for test class [%s]: %s"
.formatted(testClass.getSimpleName(), classSimpleNames(factories)));
}
return factories;
}
/**
* Get the {@link ContextCustomizerFactory} instances for this bootstrapper.
* <p>The default implementation delegates to
@ -407,6 +471,33 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot @@ -407,6 +471,33 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
return TestContextSpringFactoriesUtils.loadFactoryImplementations(ContextCustomizerFactory.class);
}
@SuppressWarnings("unchecked")
private List<ContextCustomizerFactory> instantiateCustomizerFactories(Class<? extends ContextCustomizerFactory>... classes) {
List<ContextCustomizerFactory> factories = new ArrayList<>(classes.length);
for (Class<? extends ContextCustomizerFactory> factoryClass : classes) {
try {
factories.add(BeanUtils.instantiateClass(factoryClass));
}
catch (BeanInstantiationException ex) {
Throwable cause = ex.getCause();
if (cause instanceof ClassNotFoundException || cause instanceof NoClassDefFoundError) {
if (logger.isDebugEnabled()) {
logger.debug("""
Skipping candidate %1$s [%2$s] due to a missing dependency. \
Specify custom %1$s classes or make the default %1$s classes \
and their required dependencies available. Offending class: [%3$s]"""
.formatted(ContextCustomizerFactory.class.getSimpleName(), factoryClass.getName(),
cause.getMessage()));
}
}
else {
throw ex;
}
}
}
return factories;
}
/**
* Resolve the {@link ContextLoader} {@linkplain Class class} to use for the
* supplied list of {@link ContextConfigurationAttributes} and then instantiate

37
spring-test/src/test/java/org/springframework/test/context/customizers/BarContextCustomizerFactory.java

@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
/*
* Copyright 2002-2023 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.customizers;
import java.util.List;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory;
/**
* @author Sam Brannen
* @since 6.1
*/
class BarContextCustomizerFactory implements ContextCustomizerFactory {
@Override
public ContextCustomizer createContextCustomizer(Class<?> testClass,
List<ContextConfigurationAttributes> configAttributes) {
return (context, mergedConfig) -> context.getBeanFactory().registerSingleton("bar", "baz");
}
}

70
spring-test/src/test/java/org/springframework/test/context/customizers/ContextCustomizerDeclarativeRegistrationComposedAnnotationTests.java

@ -0,0 +1,70 @@ @@ -0,0 +1,70 @@
/*
* Copyright 2002-2023 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.customizers;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactories;
import org.springframework.test.context.ContextCustomizerFactory;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration test which verifies support for {@link ContextCustomizerFactory}
* and {@link ContextCustomizer} when a custom factory is registered declaratively
* via {@link ContextCustomizerFactories @ContextCustomizerFactories}.
*
* @author Sam Brannen
* @since 6.1
*/
@SpringJUnitConfig({})
@CustomizeWithFruit
@CustomizeWithFoo
@CustomizeWithBar
class ContextCustomizerDeclarativeRegistrationComposedAnnotationTests {
// GlobalFruitContextCustomizerFactory is registered via spring.factories
@Autowired(required = false)
@Qualifier("global$fruit")
String fruit;
@Autowired(required = false)
@Qualifier("foo")
String foo;
@Autowired(required = false)
@Qualifier("bar")
String bar;
@Test
void injectedBean() {
// registered globally via spring.factories
assertThat(fruit).isEqualTo("apple, banana, cherry");
// From local @ContextCustomizerFactories
assertThat(foo).isEqualTo("bar");
// @ContextCustomizerFactories is not currently supported as a repeatable annotation.
assertThat(bar).isNull();
}
}

77
spring-test/src/test/java/org/springframework/test/context/customizers/ContextCustomizerDeclarativeRegistrationTests.java

@ -0,0 +1,77 @@ @@ -0,0 +1,77 @@
/*
* Copyright 2002-2023 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.customizers;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactories;
import org.springframework.test.context.ContextCustomizerFactory;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration test which verifies support for {@link ContextCustomizerFactory}
* and {@link ContextCustomizer} when a custom factory is registered declaratively
* via {@link ContextCustomizerFactories @ContextCustomizerFactories}.
*
* @author Sam Brannen
* @since 6.1
*/
@SpringJUnitConfig({})
@CustomizeWithFruit
@CustomizeWithFoo
@ContextCustomizerFactories(EnigmaContextCustomizerFactory.class)
@CustomizeWithBar
class ContextCustomizerDeclarativeRegistrationTests {
// GlobalFruitContextCustomizerFactory is registered via spring.factories
@Autowired(required = false)
@Qualifier("global$fruit")
String fruit;
@Autowired
Integer enigma;
@Autowired(required = false)
@Qualifier("foo")
String foo;
@Autowired(required = false)
@Qualifier("bar")
String bar;
@Test
void injectedBean() {
// registered globally via spring.factories
assertThat(fruit).isEqualTo("apple, banana, cherry");
// From local @ContextCustomizerFactories
assertThat(enigma).isEqualTo(42);
// @ContextCustomizerFactories is not currently supported as a repeatable annotation,
// and a directly present @ContextCustomizerFactories annotation overrides
// @ContextCustomizerFactories meta-annotations.
assertThat(foo).isNull();
assertThat(bar).isNull();
}
}

94
spring-test/src/test/java/org/springframework/test/context/customizers/ContextCustomizerTests.java

@ -0,0 +1,94 @@ @@ -0,0 +1,94 @@
/*
* Copyright 2002-2023 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.customizers;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.BootstrapWith;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactories;
import org.springframework.test.context.ContextCustomizerFactory;
import org.springframework.test.context.customizers.ContextCustomizerTests.EnigmaTestContextBootstrapper;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.test.context.support.AbstractTestContextBootstrapper;
import org.springframework.test.context.support.DefaultTestContextBootstrapper;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration test which verifies support for {@link ContextCustomizerFactory}
* and {@link ContextCustomizer} when a custom factory is registered by overriding
* {@link AbstractTestContextBootstrapper#getContextCustomizerFactories} and
* additional factories are registered declaratively via
* {@link ContextCustomizerFactories @ContextCustomizerFactories}.
*
* @author Sam Brannen
* @author Phillip Webb
* @since 4.3
*/
@SpringJUnitConfig({})
@CustomizeWithFoo
@BootstrapWith(EnigmaTestContextBootstrapper.class)
@CustomizeWithBar
class ContextCustomizerTests {
// GlobalFruitContextCustomizerFactory is registered via spring.factories
@Autowired(required = false)
@Qualifier("global$fruit")
String fruit;
@Autowired
Integer enigma;
@Autowired(required = false)
@Qualifier("foo")
String foo;
@Autowired(required = false)
@Qualifier("bar")
String bar;
@Test
void injectedBean() {
// Local Bootstrapper overrides spring.factories lookup
assertThat(fruit).isNull();
// From local Bootstrapper
assertThat(enigma).isEqualTo(42);
// From local @ContextCustomizerFactories
assertThat(foo).isEqualTo("bar");
// @ContextCustomizerFactories is not currently supported as a repeatable annotation.
assertThat(bar).isNull();
}
static class EnigmaTestContextBootstrapper extends DefaultTestContextBootstrapper {
@Override
protected List<ContextCustomizerFactory> getContextCustomizerFactories() {
return List.of(new EnigmaContextCustomizerFactory());
}
}
}

34
spring-test/src/test/java/org/springframework/test/context/customizers/CustomizeWithBar.java

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
/*
* Copyright 2002-2023 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.customizers;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.test.context.ContextCustomizerFactories;
/**
* @author Sam Brannen
* @since 6.1
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@ContextCustomizerFactories(BarContextCustomizerFactory.class)
@interface CustomizeWithBar {
}

34
spring-test/src/test/java/org/springframework/test/context/customizers/CustomizeWithFoo.java

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
/*
* Copyright 2002-2023 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.customizers;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.test.context.ContextCustomizerFactories;
/**
* @author Sam Brannen
* @since 6.1
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@ContextCustomizerFactories(FooContextCustomizerFactory.class)
@interface CustomizeWithFoo {
}

35
spring-test/src/test/java/org/springframework/test/context/customizers/CustomizeWithFruit.java

@ -0,0 +1,35 @@ @@ -0,0 +1,35 @@
/*
* Copyright 2002-2023 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.customizers;
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;
/**
* Marker annotation to trigger {@link GlobalFruitContextCustomizerFactory}.
*
* @author Sam Brannen
* @since 6.1
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
@interface CustomizeWithFruit {
}

37
spring-test/src/test/java/org/springframework/test/context/customizers/EnigmaContextCustomizerFactory.java

@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
/*
* Copyright 2002-2023 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.customizers;
import java.util.List;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory;
/**
* @author Sam Brannen
* @since 6.1
*/
class EnigmaContextCustomizerFactory implements ContextCustomizerFactory {
@Override
public ContextCustomizer createContextCustomizer(Class<?> testClass,
List<ContextConfigurationAttributes> configAttributes) {
return (context, mergedConfig) -> context.getBeanFactory().registerSingleton("enigma", 42);
}
}

37
spring-test/src/test/java/org/springframework/test/context/customizers/FooContextCustomizerFactory.java

@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
/*
* Copyright 2002-2023 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.customizers;
import java.util.List;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory;
/**
* @author Sam Brannen
* @since 6.1
*/
class FooContextCustomizerFactory implements ContextCustomizerFactory {
@Override
public ContextCustomizer createContextCustomizer(Class<?> testClass,
List<ContextConfigurationAttributes> configAttributes) {
return (context, mergedConfig) -> context.getBeanFactory().registerSingleton("foo", "bar");
}
}

65
spring-test/src/test/java/org/springframework/test/context/customizers/GlobalFruitContextCustomizerFactory.java

@ -0,0 +1,65 @@ @@ -0,0 +1,65 @@
/*
* Copyright 2002-2023 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.customizers;
import java.util.List;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.lang.Nullable;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory;
import org.springframework.test.context.MergedContextConfiguration;
/**
* @author Sam Brannen
* @since 6.1
*/
class GlobalFruitContextCustomizerFactory implements ContextCustomizerFactory {
@Override
public ContextCustomizer createContextCustomizer(Class<?> testClass,
List<ContextConfigurationAttributes> configAttributes) {
if (testClass.isAnnotationPresent(CustomizeWithFruit.class)) {
return new GlobalFruitContextCustomizer();
}
return null;
}
}
class GlobalFruitContextCustomizer implements ContextCustomizer {
@Override
public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
// Use "global$fruit" as the bean name instead of something simple like "fruit"
// to avoid bean name clashes with any test that registers a bean named "fruit".
context.getBeanFactory().registerSingleton("global$fruit", "apple, banana, cherry");
}
@Override
public boolean equals(@Nullable Object other) {
return (this == other || (other != null && getClass() == other.getClass()));
}
@Override
public int hashCode() {
return getClass().hashCode();
}
}

46
spring-test/src/test/java/org/springframework/test/context/customizers/InheritedContextCustomizerRegistrationTests.java

@ -0,0 +1,46 @@ @@ -0,0 +1,46 @@
/*
* Copyright 2002-2023 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.customizers;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Sam Brannen
* @since 6.1
*/
@CustomizeWithBar
class InheritedContextCustomizerRegistrationTests extends LocalContextCustomizerRegistrationTests {
@Autowired
@Qualifier("bar")
String bar;
@Override
@Test
void injectedBean() {
assertThat(fruit).isEqualTo("apple, banana, cherry");
assertThat(foo).isEqualTo("bar");
assertThat(bar).isEqualTo("baz");
}
}

52
spring-test/src/test/java/org/springframework/test/context/customizers/LocalContextCustomizerRegistrationTests.java

@ -0,0 +1,52 @@ @@ -0,0 +1,52 @@
/*
* Copyright 2002-2023 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.customizers;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Sam Brannen
* @since 6.1
*/
@SpringJUnitConfig({})
@CustomizeWithFruit
@CustomizeWithFoo
class LocalContextCustomizerRegistrationTests {
// GlobalFruitContextCustomizerFactory is registered via spring.factories
@Autowired(required = false)
@Qualifier("global$fruit")
String fruit;
@Autowired(required = false)
@Qualifier("foo")
String foo;
@Test
void injectedBean() {
assertThat(fruit).isEqualTo("apple, banana, cherry");
assertThat(foo).isEqualTo("bar");
}
}

57
spring-test/src/test/java/org/springframework/test/context/customizers/OverriddenContextCustomizerRegistrationTests.java

@ -0,0 +1,57 @@ @@ -0,0 +1,57 @@
/*
* Copyright 2002-2023 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.customizers;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextCustomizerFactories;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Sam Brannen
* @since 6.1
*/
@ContextCustomizerFactories(factories = {BarContextCustomizerFactory.class, EnigmaContextCustomizerFactory.class},
inheritFactories = false)
class OverriddenContextCustomizerRegistrationTests extends LocalContextCustomizerRegistrationTests {
@Autowired
@Qualifier("bar")
String bar;
@Autowired
Integer enigma;
@Override
@Test
void injectedBean() {
// globally registered via spring.factories
assertThat(fruit).isEqualTo("apple, banana, cherry");
// Overridden by this subclass (inheritFactories = false)
assertThat(foo).isNull();
// Local to this subclass
assertThat(bar).isEqualTo("baz");
assertThat(enigma).isEqualTo(42);
}
}

62
spring-test/src/test/java/org/springframework/test/context/customizers/ReplaceDefaultsContextCustomizerTests.java

@ -0,0 +1,62 @@ @@ -0,0 +1,62 @@
/*
* Copyright 2002-2023 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.customizers;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextCustomizerFactories;
import org.springframework.test.context.ContextCustomizerFactories.MergeMode;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Sam Brannen
* @since 6.1
*/
@SpringJUnitConfig({})
@ContextCustomizerFactories(factories = {FooContextCustomizerFactory.class, BarContextCustomizerFactory.class},
mergeMode = MergeMode.REPLACE_DEFAULTS)
class ReplaceDefaultsContextCustomizerTests {
// GlobalFruitContextCustomizerFactory is registered via spring.factories
@Autowired(required = false)
@Qualifier("global$fruit")
String fruit;
@Autowired(required = false)
@Qualifier("foo")
String foo;
@Autowired(required = false)
@Qualifier("bar")
String bar;
@Test
void injectedBean() {
// MergeMode.REPLACE_DEFAULTS overrides spring.factories lookup
assertThat(fruit).isNull();
// From local @ContextCustomizerFactories
assertThat(foo).isEqualTo("bar");
assertThat(bar).isEqualTo("baz");
}
}

66
spring-test/src/test/java/org/springframework/test/context/junit4/ContextCustomizerSpringRunnerTests.java

@ -1,66 +0,0 @@ @@ -1,66 +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.junit4;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.BootstrapWith;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory;
import org.springframework.test.context.junit4.ContextCustomizerSpringRunnerTests.CustomTestContextBootstrapper;
import org.springframework.test.context.support.DefaultTestContextBootstrapper;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
/**
* JUnit 4 based integration test which verifies support of
* {@link ContextCustomizerFactory} and {@link ContextCustomizer}.
*
* @author Sam Brannen
* @author Phillip Webb
* @since 4.3
*/
@RunWith(SpringRunner.class)
@BootstrapWith(CustomTestContextBootstrapper.class)
public class ContextCustomizerSpringRunnerTests {
@Autowired String foo;
@Test
public void injectedBean() {
assertThat(foo).isEqualTo("foo");
}
static class CustomTestContextBootstrapper extends DefaultTestContextBootstrapper {
@Override
protected List<ContextCustomizerFactory> getContextCustomizerFactories() {
return singletonList(
(ContextCustomizerFactory) (testClass, configAttributes) ->
(ContextCustomizer) (context, mergedConfig) -> context.getBeanFactory().registerSingleton("foo", "foo")
);
}
}
}

3
spring-test/src/test/resources/META-INF/spring.factories

@ -4,7 +4,8 @@ @@ -4,7 +4,8 @@
org.springframework.test.context.TestExecutionListener = org.example.FooListener
org.springframework.test.context.ContextCustomizerFactory =\
org.springframework.test.context.aot.samples.basic.ImportsContextCustomizerFactory
org.springframework.test.context.aot.samples.basic.ImportsContextCustomizerFactory,\
org.springframework.test.context.customizers.GlobalFruitContextCustomizerFactory
org.springframework.test.context.ApplicationContextFailureProcessor =\
org.springframework.test.context.failures.TrackingApplicationContextFailureProcessor

Loading…
Cancel
Save