Skip to content

Commit a220c54

Browse files
committed
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
1 parent 99a50e7 commit a220c54

27 files changed

+1085
-70
lines changed

framework-docs/modules/ROOT/nav.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@
122122
**** xref:testing/testcontext-framework/ctx-management/groovy.adoc[]
123123
**** xref:testing/testcontext-framework/ctx-management/javaconfig.adoc[]
124124
**** xref:testing/testcontext-framework/ctx-management/mixed-config.adoc[]
125+
**** xref:testing/testcontext-framework/ctx-management/context-customizers.adoc[]
125126
**** xref:testing/testcontext-framework/ctx-management/initializers.adoc[]
126127
**** xref:testing/testcontext-framework/ctx-management/inheritance.adoc[]
127128
**** xref:testing/testcontext-framework/ctx-management/env-profiles.adoc[]
@@ -166,6 +167,7 @@
166167
***** xref:testing/annotations/integration-spring/annotation-contextconfiguration.adoc[]
167168
***** xref:testing/annotations/integration-spring/annotation-webappconfiguration.adoc[]
168169
***** xref:testing/annotations/integration-spring/annotation-contexthierarchy.adoc[]
170+
***** xref:testing/annotations/integration-spring/annotation-contextcustomizerfactories.adoc[]
169171
***** xref:testing/annotations/integration-spring/annotation-activeprofiles.adoc[]
170172
***** xref:testing/annotations/integration-spring/annotation-testpropertysource.adoc[]
171173
***** xref:testing/annotations/integration-spring/annotation-dynamicpropertysource.adoc[]

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ following annotations.
223223
* xref:testing/annotations/integration-spring/annotation-contextconfiguration.adoc[`@ContextConfiguration`]
224224
* xref:testing/annotations/integration-spring/annotation-webappconfiguration.adoc[`@WebAppConfiguration`]
225225
* xref:testing/annotations/integration-spring/annotation-contexthierarchy.adoc[`@ContextHierarchy`]
226+
* xref:testing/annotations/integration-spring/annotation-contextcustomizerfactories.adoc[`@ContextCustomizerFactories`]
226227
* xref:testing/annotations/integration-spring/annotation-activeprofiles.adoc[`@ActiveProfiles`]
227228
* xref:testing/annotations/integration-spring/annotation-testpropertysource.adoc[`@TestPropertySource`]
228229
* xref:testing/annotations/integration-spring/annotation-dynamicpropertysource.adoc[`@DynamicPropertySource`]

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ xref:testing/testcontext-framework.adoc[TestContext framework].
1111
* `@BootstrapWith`
1212
* `@ContextConfiguration`
1313
* `@ContextHierarchy`
14+
* `@ContextCustomizerFactories`
1415
* `@ActiveProfiles`
1516
* `@TestPropertySource`
1617
* `@DirtiesContext`

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Spring's testing annotations include the following:
1212
* xref:testing/annotations/integration-spring/annotation-contextconfiguration.adoc[`@ContextConfiguration`]
1313
* xref:testing/annotations/integration-spring/annotation-webappconfiguration.adoc[`@WebAppConfiguration`]
1414
* xref:testing/annotations/integration-spring/annotation-contexthierarchy.adoc[`@ContextHierarchy`]
15+
* xref:testing/annotations/integration-spring/annotation-contextcustomizerfactories.adoc[`@ContextCustomizerFactories`]
1516
* xref:testing/annotations/integration-spring/annotation-activeprofiles.adoc[`@ActiveProfiles`]
1617
* xref:testing/annotations/integration-spring/annotation-testpropertysource.adoc[`@TestPropertySource`]
1718
* xref:testing/annotations/integration-spring/annotation-dynamicpropertysource.adoc[`@DynamicPropertySource`]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
[[spring-testing-annotation-contextcustomizerfactories]]
2+
= `@ContextCustomizerFactories`
3+
4+
`@ContextCustomizerFactories` is used to register `ContextCustomizerFactory`
5+
implementations for a particular test class, its subclasses, and its nested classes. If
6+
you wish to register a factory globally, you should register it via the automatic
7+
discovery mechanism described in
8+
xref:testing/testcontext-framework/ctx-management/context-customizers.adoc[`ContextCustomizerFactory` Configuration].
9+
10+
The following example shows how to register two `ContextCustomizerFactory` implementations:
11+
12+
[tabs]
13+
======
14+
Java::
15+
+
16+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
17+
----
18+
@ContextConfiguration
19+
@ContextCustomizerFactories({CustomContextCustomizerFactory.class, AnotherContextCustomizerFactory.class}) // <1>
20+
class CustomContextCustomizerFactoryTests {
21+
// class body...
22+
}
23+
----
24+
<1> Register two `ContextCustomizerFactory` implementations.
25+
26+
Kotlin::
27+
+
28+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
29+
----
30+
@ContextConfiguration
31+
@ContextCustomizerFactories([CustomContextCustomizerFactory::class, AnotherContextCustomizerFactory::class]) // <1>
32+
class CustomContextCustomizerFactoryTests {
33+
// class body...
34+
}
35+
----
36+
<1> Register two `ContextCustomizerFactory` implementations.
37+
======
38+
39+
40+
By default, `@ContextCustomizerFactories` provides support for inheriting factories from
41+
superclasses or enclosing classes. See
42+
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-nested-test-configuration[`@Nested` test class configuration] and the
43+
{api-spring-framework}/test/context/ContextCustomizerFactories.html[`@ContextCustomizerFactories`
44+
javadoc] for an example and further details.
45+

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ advanced use cases.
112112
* xref:testing/testcontext-framework/ctx-management/groovy.adoc[Context Configuration with Groovy Scripts]
113113
* xref:testing/testcontext-framework/ctx-management/javaconfig.adoc[Context Configuration with Component Classes]
114114
* xref:testing/testcontext-framework/ctx-management/mixed-config.adoc[Mixing XML, Groovy Scripts, and Component Classes]
115+
* xref:testing/testcontext-framework/ctx-management/context-customizers.adoc[Context Configuration with Context Customizers]
115116
* xref:testing/testcontext-framework/ctx-management/initializers.adoc[Context Configuration with Context Initializers]
116117
* xref:testing/testcontext-framework/ctx-management/inheritance.adoc[Context Configuration Inheritance]
117118
* xref:testing/testcontext-framework/ctx-management/env-profiles.adoc[Context Configuration with Environment Profiles]
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
[[testcontext-context-customizers]]
2+
= Configuration Configuration with Context Customizers
3+
4+
A `ContextCustomizer` is responsible for customizing the supplied
5+
`ConfigurableApplicationContext` after bean definitions have been loaded into the context
6+
but before the context has been refreshed.
7+
8+
A `ContextCustomizerFactory` is responsible for creating a `ContextCustomizer`, based on
9+
some custom logic which determines if the `ContextCustomizer` is necessary for a given
10+
test class -- for example, based on the presence of a certain annotation. Factories are
11+
invoked after `ContextLoaders` have processed context configuration attributes for a test
12+
class but before the `MergedContextConfiguration` is created.
13+
14+
For example, Spring Framework provides the following `ContextCustomizerFactory`
15+
implementation which is registered by default:
16+
17+
`MockServerContainerContextCustomizerFactory`:: Creates a
18+
`MockServerContainerContextCustomizer` if WebSocket support is present in the classpath
19+
and the test class or one of its enclosing classes is annotated or meta-annotated with
20+
`@WebAppConfiguration`. `MockServerContainerContextCustomizer` instantiates a new
21+
`MockServerContainer` and stores it in the `ServletContext` under the attribute named
22+
`jakarta.websocket.server.ServerContainer`.
23+
24+
25+
[[testcontext-context-customizers-registration]]
26+
== Registering `ContextCustomizerFactory` Implementations
27+
28+
You can register `ContextCustomizerFactory` implementations explicitly for a test class, its
29+
subclasses, and its nested classes by using the `@ContextCustomizerFactories` annotation. See
30+
xref:testing/annotations/integration-spring/annotation-contextcustomizerfactories.adoc[annotation support]
31+
and the javadoc for
32+
{api-spring-framework}/test/context/ContextCustomizerFactories.html[`@ContextCustomizerFactories`]
33+
for details and examples.
34+
35+
36+
[[testcontext-context-customizers-automatic-discovery]]
37+
== Automatic Discovery of Default `ContextCustomizerFactory` Implementations
38+
39+
Registering `ContextCustomizerFactory` implementations by using `@ContextCustomizerFactories` is
40+
suitable for custom factories that are used in limited testing scenarios. However, it can
41+
become cumbersome if a custom factory needs to be used across an entire test suite. This
42+
issue is addressed through support for automatic discovery of default
43+
`ContextCustomizerFactory` implementations through the `SpringFactoriesLoader` mechanism.
44+
45+
Specifically, the modules that make up the testing support in Spring Framework and Spring
46+
Boot declare all core default `ContextCustomizerFactory` implementations under the
47+
`org.springframework.test.context.ContextCustomizerFactory` key in their
48+
`META-INF/spring.factories` properties files. Third-party frameworks and developers can
49+
contribute their own `ContextCustomizerFactory` implementations to the list of default
50+
factories in the same manner through their own `META-INF/spring.factories` properties
51+
files.
52+
53+
54+
[[testcontext-context-customizers-merging]]
55+
== Merging `ContextCustomizerFactory` Implementations
56+
57+
If a custom `ContextCustomizerFactory` is registered via `@ContextCustomizerFactories`, it
58+
will be _merged_ with the default factories that have been registered using the aforementioned
59+
xref:testing/testcontext-framework/ctx-management/context-customizers.adoc#testcontext-context-customizers-automatic-discovery[automatic discovery mechanism].
60+
61+
The merging algorithm ensures that duplicates are removed from the list and that locally
62+
declared factories are appended to the list of default factories when merged.
63+
64+
[TIP]
65+
====
66+
To replace the default factories for a test class, its subclasses, and its nested
67+
classes, you can set the `mergeMode` attribute of `@ContextCustomizerFactories` to
68+
`MergeMode.REPLACE_DEFAULTS`.
69+
====

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
* @author Sam Brannen
3535
* @since 4.3
3636
* @see ContextCustomizerFactory
37+
* @see ContextCustomizerFactories @ContextCustomizerFactories
3738
* @see org.springframework.test.context.support.AbstractContextLoader#customizeContext
3839
*/
3940
@FunctionalInterface
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
* Copyright 2002-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.test.context;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Inherited;
22+
import java.lang.annotation.Retention;
23+
import java.lang.annotation.RetentionPolicy;
24+
import java.lang.annotation.Target;
25+
26+
import org.springframework.core.annotation.AliasFor;
27+
28+
/**
29+
* {@code @ContextCustomizerFactories} defines class-level metadata for configuring
30+
* which {@link ContextCustomizerFactory} implementations should be registered with
31+
* the <em>Spring TestContext Framework</em>.
32+
*
33+
* <p>{@code @ContextCustomizerFactories} is used to register factories for a
34+
* particular test class, its subclasses, and its nested classes. If you wish to
35+
* register a factory globally, you should register it via the automatic discovery
36+
* mechanism described in {@link ContextCustomizerFactory}.
37+
*
38+
* <p>This annotation may be used as a <em>meta-annotation</em> to create custom
39+
* <em>composed annotations</em>. In addition, this annotation will be inherited
40+
* from an enclosing test class by default. See
41+
* {@link NestedTestConfiguration @NestedTestConfiguration} for details.
42+
*
43+
* @author Sam Brannen
44+
* @since 6.1
45+
* @see ContextCustomizerFactory
46+
* @see ContextCustomizer
47+
*/
48+
@Target(ElementType.TYPE)
49+
@Retention(RetentionPolicy.RUNTIME)
50+
@Documented
51+
@Inherited
52+
public @interface ContextCustomizerFactories {
53+
54+
/**
55+
* Alias for {@link #factories}.
56+
* <p>This attribute may <strong>not</strong> be used in conjunction with
57+
* {@link #factories}, but it may be used instead of {@link #factories}.
58+
*/
59+
@AliasFor("factories")
60+
Class<? extends ContextCustomizerFactory>[] value() default {};
61+
62+
/**
63+
* The {@link ContextCustomizerFactory} implementations to register.
64+
* <p>This attribute may <strong>not</strong> be used in conjunction with
65+
* {@link #value}, but it may be used instead of {@link #value}.
66+
*/
67+
@AliasFor("value")
68+
Class<? extends ContextCustomizerFactory>[] factories() default {};
69+
70+
/**
71+
* Whether the configured set of {@link #factories} from superclasses and
72+
* enclosing classes should be <em>inherited</em>.
73+
* <p>The default value is {@code true}, which means that an annotated class
74+
* will <em>inherit</em> the factories defined by an annotated superclass or
75+
* enclosing class. Specifically, the factories for an annotated class will be
76+
* appended to the list of factories defined by an annotated superclass or
77+
* enclosing class. Thus, subclasses and nested classes have the option of
78+
* <em>extending</em> the list of factories.
79+
* <p>If {@code inheritListeners} is set to {@code false}, the factories for
80+
* the annotated class will <em>shadow</em> and effectively replace any
81+
* factories defined by a superclass or enclosing class.
82+
*/
83+
boolean inheritFactories() default true;
84+
85+
/**
86+
* The <em>merge mode</em> to use when {@code @ContextCustomizerFactories} is
87+
* declared on a class that does <strong>not</strong> inherit factories from
88+
* a superclass or enclosing class.
89+
* <p>Can be set to {@link MergeMode#REPLACE_DEFAULTS REPLACE_DEFAULTS} to
90+
* have locally declared factories replace the default factories.
91+
* <p>The mode is ignored if factories are inherited from a superclass or
92+
* enclosing class.
93+
* <p>Defaults to {@link MergeMode#MERGE_WITH_DEFAULTS MERGE_WITH_DEFAULTS}.
94+
* @see MergeMode
95+
*/
96+
MergeMode mergeMode() default MergeMode.MERGE_WITH_DEFAULTS;
97+
98+
99+
/**
100+
* Enumeration of <em>modes</em> that dictate whether explicitly declared
101+
* factories are merged with the default factories when
102+
* {@code @ContextCustomizerFactories} is declared on a class that does
103+
* <strong>not</strong> inherit factories from a superclass or enclosing
104+
* class.
105+
*/
106+
enum MergeMode {
107+
108+
/**
109+
* Indicates that locally declared factories should be merged with the
110+
* default factories.
111+
* <p>The merging algorithm ensures that duplicates are removed from the
112+
* list and that locally declared factories are appended to the list of
113+
* default factories when merged.
114+
*/
115+
MERGE_WITH_DEFAULTS,
116+
117+
/**
118+
* Indicates that locally declared factories should replace the default
119+
* factories.
120+
*/
121+
REPLACE_DEFAULTS
122+
123+
}
124+
125+
}

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,12 +29,18 @@
2929
*
3030
* <p>By default, the Spring TestContext Framework will use the
3131
* {@link org.springframework.core.io.support.SpringFactoriesLoader SpringFactoriesLoader}
32-
* mechanism for loading factories configured in all {@code META-INF/spring.factories}
32+
* mechanism for loading default factories configured in all {@code META-INF/spring.factories}
3333
* files on the classpath.
3434
*
35+
* <p>As of Spring Framework 6.1, it is also possible to register factories
36+
* declaratively via the {@link ContextCustomizerFactories @ContextCustomizerFactories}
37+
* annotation.
38+
*
3539
* @author Phillip Webb
3640
* @author Sam Brannen
3741
* @since 4.3
42+
* @see ContextCustomizer
43+
* @see ContextCustomizerFactories @ContextCustomizerFactories
3844
*/
3945
@FunctionalInterface
4046
public interface ContextCustomizerFactory {

0 commit comments

Comments
 (0)