Skip to content

Commit 66250b1

Browse files
committed
Support merging custom TELs with default TELs
Prior to this commit, if a custom TestExecutionListener was registered via @TestExecutionListeners the defaults would not be registered. Thus, if a user wanted to declare a custom listener and use the default listeners, the user was forced to manually declare all default listeners in addition to any custom listeners. This unfortunately required that the user know exactly which listeners were registered by default. Moreover, the set of default listeners can change from release to release, and with the support for automatic discovery of default listeners introduced in SPR-11466 it is no longer even possible to know what the set of default TestExecutionListeners is before runtime. This commit addresses this issue by introducing a mechanism for merging custom declared listeners with the defaults for the current environment. Specifically, @TestExecutionListeners supports a new MergeMode that is used to control whether or not explicitly declared listeners are merged with the default listeners when @TestExecutionListeners is declared on a class that does not inherit listeners from a superclass. Issue: SPR-8854
1 parent b3add79 commit 66250b1

File tree

3 files changed

+236
-84
lines changed

3 files changed

+236
-84
lines changed

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

+57-8
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@
2424
import java.lang.annotation.Target;
2525

2626
/**
27-
* {@code TestExecutionListeners} defines class-level metadata for
28-
* configuring which {@link TestExecutionListener TestExecutionListeners} should
29-
* be registered with a {@link TestContextManager}.
27+
* {@code TestExecutionListeners} defines class-level metadata for configuring
28+
* which {@link TestExecutionListener TestExecutionListeners} should be
29+
* registered with a {@link TestContextManager}.
3030
*
3131
* <p>Typically, {@code @TestExecutionListeners} will be used in conjunction with
3232
* {@link ContextConfiguration @ContextConfiguration}.
@@ -47,7 +47,40 @@
4747
public @interface TestExecutionListeners {
4848

4949
/**
50-
* Alias for {@link #listeners() listeners}.
50+
* Enumeration of <em>modes</em> that dictate whether or not explicitly
51+
* declared listeners are merged with the default listeners when
52+
* {@code @TestExecutionListeners} is declared on a class that does
53+
* <strong>not</strong> inherit listeners from a superclass.
54+
* @since 4.1
55+
*/
56+
static enum MergeMode {
57+
58+
/**
59+
* Indicates that locally declared listeners should replace the default
60+
* listeners.
61+
*/
62+
REPLACE_DEFAULTS,
63+
64+
/**
65+
* Indicates that locally declared listeners should be merged with the
66+
* default listeners.
67+
* <p>The merging algorithm ensures that duplicates are removed from
68+
* the list and that the resulting set of merged listeners is sorted
69+
* according to the semantics of
70+
* {@link org.springframework.core.annotation.AnnotationAwareOrderComparator
71+
* AnnotationAwareOrderComparator}. If a listener implements
72+
* {@link org.springframework.core.Ordered Ordered} or is annotated
73+
* with {@link org.springframework.core.annotation.Order @Order} it can
74+
* influence the position in which it is merged with the defaults; otherwise,
75+
* locally declared listeners will simply be appended to the list of default
76+
* listeners when merged.
77+
*/
78+
MERGE_WITH_DEFAULTS,
79+
}
80+
81+
82+
/**
83+
* Alias for {@link #listeners}.
5184
*
5285
* <p>This attribute may <strong>not</strong> be used in conjunction with
5386
* {@link #listeners}, but it may be used instead of {@link #listeners}.
@@ -56,7 +89,7 @@
5689

5790
/**
5891
* The {@link TestExecutionListener TestExecutionListeners} to register with
59-
* a {@link TestContextManager}.
92+
* the {@link TestContextManager}.
6093
*
6194
* <p>This attribute may <strong>not</strong> be used in conjunction with
6295
* {@link #value}, but it may be used instead of {@link #value}.
@@ -65,14 +98,15 @@
6598
* @see org.springframework.test.context.support.DependencyInjectionTestExecutionListener
6699
* @see org.springframework.test.context.support.DirtiesContextTestExecutionListener
67100
* @see org.springframework.test.context.transaction.TransactionalTestExecutionListener
101+
* @see org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener
68102
*/
69103
Class<? extends TestExecutionListener>[] listeners() default {};
70104

71105
/**
72-
* Whether or not {@link #value() TestExecutionListeners} from superclasses
106+
* Whether or not {@link #listeners TestExecutionListeners} from superclasses
73107
* should be <em>inherited</em>.
74-
* <p>
75-
* The default value is {@code true}, which means that an annotated
108+
*
109+
* <p>The default value is {@code true}, which means that an annotated
76110
* class will <em>inherit</em> the listeners defined by an annotated
77111
* superclass. Specifically, the listeners for an annotated class will be
78112
* appended to the list of listeners defined by an annotated superclass.
@@ -106,4 +140,19 @@
106140
*/
107141
boolean inheritListeners() default true;
108142

143+
/**
144+
* The <em>merge mode</em> to use when {@code @TestExecutionListeners} is
145+
* declared on a class that does <strong>not</strong> inherit listeners
146+
* from a superclass.
147+
* <p>Can be set to {@link MergeMode#MERGE_WITH_DEFAULTS MERGE_WITH_DEFAULTS}
148+
* to have locally declared listeners <em>merged</em> with the default
149+
* listeners.
150+
* <p>The mode is ignored if listeners are inherited from a superclass.
151+
* <p>Defaults to {@link MergeMode#REPLACE_DEFAULTS REPLACE_DEFAULTS}
152+
* for backwards compatibility.
153+
* @see MergeMode
154+
* @since 4.1
155+
*/
156+
MergeMode mergeMode() default MergeMode.REPLACE_DEFAULTS;
157+
109158
}

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

+29-4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.ArrayList;
2020
import java.util.Arrays;
2121
import java.util.Collections;
22+
import java.util.HashSet;
2223
import java.util.LinkedHashSet;
2324
import java.util.List;
2425
import java.util.Map;
@@ -45,6 +46,7 @@
4546
import org.springframework.test.context.TestContextBootstrapper;
4647
import org.springframework.test.context.TestExecutionListener;
4748
import org.springframework.test.context.TestExecutionListeners;
49+
import org.springframework.test.context.TestExecutionListeners.MergeMode;
4850
import org.springframework.test.util.MetaAnnotationUtils;
4951
import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor;
5052
import org.springframework.util.Assert;
@@ -137,14 +139,37 @@ else if (!ObjectUtils.isEmpty(valueListenerClasses)) {
137139
listenerClasses = valueListenerClasses;
138140
}
139141

140-
if (listenerClasses != null) {
141-
classesList.addAll(0, Arrays.<Class<? extends TestExecutionListener>> asList(listenerClasses));
142+
boolean inheritListeners = annAttrs.getBoolean("inheritListeners");
143+
AnnotationDescriptor<TestExecutionListeners> superDescriptor = MetaAnnotationUtils.findAnnotationDescriptor(
144+
descriptor.getRootDeclaringClass().getSuperclass(), annotationType);
145+
146+
// If there are no listeners to inherit, we might need to merge the
147+
// locally declared listeners with the defaults.
148+
if ((!inheritListeners || superDescriptor == null)
149+
&& (annAttrs.getEnum("mergeMode") == MergeMode.MERGE_WITH_DEFAULTS)) {
150+
if (logger.isDebugEnabled()) {
151+
logger.debug(String.format(
152+
"Merging default listeners with listeners configured via @TestExecutionListeners for class [%s].",
153+
clazz.getName()));
154+
}
155+
usingDefaults = true;
156+
classesList.addAll(getDefaultTestExecutionListenerClasses());
142157
}
143-
descriptor = (annAttrs.getBoolean("inheritListeners") ? MetaAnnotationUtils.findAnnotationDescriptor(
144-
descriptor.getRootDeclaringClass().getSuperclass(), annotationType) : null);
158+
159+
classesList.addAll(0, Arrays.<Class<? extends TestExecutionListener>> asList(listenerClasses));
160+
161+
descriptor = (inheritListeners ? superDescriptor : null);
145162
}
146163
}
147164

165+
// Remove possible duplicates if we loaded default listeners.
166+
if (usingDefaults) {
167+
Set<Class<? extends TestExecutionListener>> classesSet = new HashSet<Class<? extends TestExecutionListener>>();
168+
classesSet.addAll(classesList);
169+
classesList.clear();
170+
classesList.addAll(classesSet);
171+
}
172+
148173
List<TestExecutionListener> listeners = instantiateListeners(classesList);
149174

150175
// Sort by Ordered/@Order if we loaded default listeners.

0 commit comments

Comments
 (0)