Skip to content

Commit e086a63

Browse files
committed
Introduce BEFORE METHOD/CLASS modes in @DirtiesContext
Prior to this commit, @DirtiesContext could only be used to close a test ApplicationContext after an entire test class or after a test method; however, there are some use cases for which it would be beneficial to close a test ApplicationContext before a given test class or test method -- for example, if some rogue (i.e., yet to be determined) test within a large test suite has corrupted the original configuration for the ApplicationContext. This commit provides a solution to such testing challenges by introducing the following modes for @DirtiesContext. - MethodMode.BEFORE_METHOD: configured via the new methodMode attribute - ClassMode.BEFORE_CLASS and ClassMode.BEFORE_EACH_TEST_METHOD: both configured via the existing classMode attribute Issue: SPR-12429
1 parent 6028a64 commit e086a63

File tree

4 files changed

+389
-116
lines changed

4 files changed

+389
-116
lines changed

spring-test/src/main/java/org/springframework/test/annotation/DirtiesContext.java

Lines changed: 86 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 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.
@@ -26,43 +26,77 @@
2626
/**
2727
* Test annotation which indicates that the
2828
* {@link org.springframework.context.ApplicationContext ApplicationContext}
29-
* associated with a test is <em>dirty</em> and should be closed:
29+
* associated with a test is <em>dirty</em> and should therefore be closed
30+
* and removed from the context cache.
3031
*
31-
* <ul>
32-
* <li>after the current test, when declared at the method level</li>
33-
* <li>after each test method in the current test class, when declared at the
34-
* class level with class mode set to {@link ClassMode#AFTER_EACH_TEST_METHOD
35-
* AFTER_EACH_TEST_METHOD}</li>
36-
* <li>after the current test class, when declared at the class level with class
37-
* mode set to {@link ClassMode#AFTER_CLASS AFTER_CLASS}</li>
38-
* </ul>
39-
*
40-
* <p>Use this annotation if a test has modified the context &mdash; for example,
41-
* by replacing a bean definition or changing the state of a singleton bean.
42-
* Subsequent tests will be supplied a new context.
32+
* <p>Use this annotation if a test has modified the context &mdash; for
33+
* example, by modifying the state of a singleton bean, modifying the state
34+
* of an embedded database, etc. Subsequent tests that request the same
35+
* context will be supplied a new context.
4336
*
4437
* <p>{@code @DirtiesContext} may be used as a class-level and method-level
45-
* annotation within the same class. In such scenarios, the
46-
* {@code ApplicationContext} will be marked as <em>dirty</em> after any
47-
* such annotated method as well as after the entire class. If the
48-
* {@link ClassMode} is set to {@link ClassMode#AFTER_EACH_TEST_METHOD
49-
* AFTER_EACH_TEST_METHOD}, the context will be marked dirty after each test
50-
* method in the class.
38+
* annotation within the same class or class hierarchy. In such scenarios, the
39+
* {@code ApplicationContext} will be marked as <em>dirty</em> before or
40+
* after any such annotated method as well as before or after the current test
41+
* class, depending on the configured {@link #methodMode} and {@link #classMode}.
5142
*
5243
* <p>As of Spring Framework 4.0, this annotation may be used as a
5344
* <em>meta-annotation</em> to create custom <em>composed annotations</em>.
5445
*
46+
* <h3>Supported Test Phases</h3>
47+
* <ul>
48+
* <li><strong>Before current test class</strong>: when declared at the class
49+
* level with class mode set to {@link ClassMode#BEFORE_CLASS BEFORE_CLASS}</li>
50+
* <li><strong>Before each test method in current test class</strong>: when
51+
* declared at the class level with class mode set to
52+
* {@link ClassMode#BEFORE_EACH_TEST_METHOD BEFORE_EACH_TEST_METHOD}</li>
53+
* <li><strong>Before current test method</strong>: when declared at the
54+
* method level with method mode set to
55+
* {@link MethodMode#BEFORE_METHOD BEFORE_METHOD}</li>
56+
* <li><strong>After current test method</strong>: when declared at the
57+
* method level with method mode set to
58+
* {@link MethodMode#AFTER_METHOD AFTER_METHOD}</li>
59+
* <li><strong>After each test method in current test class</strong>: when
60+
* declared at the class level with class mode set to
61+
* {@link ClassMode#AFTER_EACH_TEST_METHOD AFTER_EACH_TEST_METHOD}</li>
62+
* <li><strong>After current test class</strong>: when declared at the
63+
* class level with class mode set to
64+
* {@link ClassMode#AFTER_CLASS AFTER_CLASS}</li>
65+
* </ul>
66+
*
5567
* @author Sam Brannen
5668
* @author Rod Johnson
5769
* @since 2.0
5870
* @see org.springframework.test.context.ContextConfiguration
71+
* @see org.springframework.test.context.support.DirtiesContextTestExecutionListener
5972
*/
6073
@Documented
6174
@Inherited
6275
@Retention(RetentionPolicy.RUNTIME)
6376
@Target({ ElementType.TYPE, ElementType.METHOD })
6477
public @interface DirtiesContext {
6578

79+
/**
80+
* Defines <i>modes</i> which determine how {@code @DirtiesContext} is
81+
* interpreted when used to annotate a test method.
82+
*
83+
* @since 4.2
84+
*/
85+
static enum MethodMode {
86+
87+
/**
88+
* The associated {@code ApplicationContext} will be marked as
89+
* <em>dirty</em> before the corresponding test method.
90+
*/
91+
BEFORE_METHOD,
92+
93+
/**
94+
* The associated {@code ApplicationContext} will be marked as
95+
* <em>dirty</em> after the corresponding test method.
96+
*/
97+
AFTER_METHOD;
98+
}
99+
66100
/**
67101
* Defines <i>modes</i> which determine how {@code @DirtiesContext} is
68102
* interpreted when used to annotate a test class.
@@ -73,15 +107,31 @@ static enum ClassMode {
73107

74108
/**
75109
* The associated {@code ApplicationContext} will be marked as
76-
* <em>dirty</em> after the test class.
110+
* <em>dirty</em> before the test class.
111+
*
112+
* @since 4.2
77113
*/
78-
AFTER_CLASS,
114+
BEFORE_CLASS,
115+
116+
/**
117+
* The associated {@code ApplicationContext} will be marked as
118+
* <em>dirty</em> before each test method in the class.
119+
*
120+
* @since 4.2
121+
*/
122+
BEFORE_EACH_TEST_METHOD,
79123

80124
/**
81125
* The associated {@code ApplicationContext} will be marked as
82126
* <em>dirty</em> after each test method in the class.
83127
*/
84-
AFTER_EACH_TEST_METHOD;
128+
AFTER_EACH_TEST_METHOD,
129+
130+
/**
131+
* The associated {@code ApplicationContext} will be marked as
132+
* <em>dirty</em> after the test class.
133+
*/
134+
AFTER_CLASS;
85135
}
86136

87137
/**
@@ -119,13 +169,23 @@ static enum HierarchyMode {
119169
}
120170

121171

172+
/**
173+
* The <i>mode</i> to use when a test method is annotated with
174+
* {@code @DirtiesContext}.
175+
* <p>Defaults to {@link MethodMode#AFTER_METHOD AFTER_METHOD}.
176+
* <p>Setting the method mode on an annotated test class has no meaning.
177+
* For class-level control, use {@link #classMode} instead.
178+
*
179+
* @since 4.2
180+
*/
181+
MethodMode methodMode() default MethodMode.AFTER_METHOD;
182+
122183
/**
123184
* The <i>mode</i> to use when a test class is annotated with
124185
* {@code @DirtiesContext}.
125186
* <p>Defaults to {@link ClassMode#AFTER_CLASS AFTER_CLASS}.
126-
* <p>Note: Setting the class mode on an annotated test method has no meaning,
127-
* since the mere presence of the {@code @DirtiesContext} annotation on a
128-
* test method is sufficient.
187+
* <p>Setting the class mode on an annotated test method has no meaning.
188+
* For method-level control, use {@link #methodMode} instead.
129189
*
130190
* @since 3.0
131191
*/
Lines changed: 113 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 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.
@@ -27,16 +27,18 @@
2727
import org.springframework.test.annotation.DirtiesContext;
2828
import org.springframework.test.annotation.DirtiesContext.ClassMode;
2929
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
30+
import org.springframework.test.annotation.DirtiesContext.MethodMode;
3031
import org.springframework.test.context.TestContext;
3132
import org.springframework.util.Assert;
3233

3334
import static org.springframework.test.annotation.DirtiesContext.ClassMode.*;
35+
import static org.springframework.test.annotation.DirtiesContext.MethodMode.*;
3436

3537
/**
3638
* {@code TestExecutionListener} which provides support for marking the
3739
* {@code ApplicationContext} associated with a test as <em>dirty</em> for
38-
* both test classes and test methods configured with the {@link DirtiesContext
39-
* &#064;DirtiesContext} annotation.
40+
* both test classes and test methods annotated with the
41+
* {@link DirtiesContext @DirtiesContext} annotation.
4042
*
4143
* @author Sam Brannen
4244
* @author Juergen Hoeller
@@ -56,20 +58,98 @@ public final int getOrder() {
5658
return 3000;
5759
}
5860

61+
/**
62+
* If the test class of the supplied {@linkplain TestContext test context}
63+
* is annotated with {@code @DirtiesContext} and the {@linkplain
64+
* DirtiesContext#classMode() class mode} is set to {@link
65+
* ClassMode#BEFORE_CLASS BEFORE_CLASS}, the {@linkplain ApplicationContext
66+
* application context} of the test context will be
67+
* {@linkplain TestContext#markApplicationContextDirty marked as dirty}, and the
68+
* {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE
69+
* REINJECT_DEPENDENCIES_ATTRIBUTE} in the test context will be set to
70+
* {@code true}.
71+
*/
72+
@Override
73+
public void beforeTestClass(TestContext testContext) throws Exception {
74+
beforeOrAfterTestClass(testContext, "Before", BEFORE_CLASS);
75+
}
76+
5977
/**
6078
* If the current test method of the supplied {@linkplain TestContext test
61-
* context} is annotated with {@link DirtiesContext &#064;DirtiesContext},
62-
* or if the test class is annotated with {@link DirtiesContext
63-
* &#064;DirtiesContext} and the {@linkplain DirtiesContext#classMode() class
64-
* mode} is set to {@link ClassMode#AFTER_EACH_TEST_METHOD
65-
* AFTER_EACH_TEST_METHOD}, the {@linkplain ApplicationContext application
66-
* context} of the test context will be
67-
* {@linkplain TestContext#markApplicationContextDirty marked as dirty} and the
68-
* {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE}
69-
* in the test context will be set to {@code true}.
79+
* context} is annotated with {@code @DirtiesContext} and the {@linkplain
80+
* DirtiesContext#methodMode() method mode} is set to {@link
81+
* MethodMode#BEFORE_METHOD BEFORE_METHOD}, or if the test class is
82+
* annotated with {@code @DirtiesContext} and the {@linkplain
83+
* DirtiesContext#classMode() class mode} is set to {@link
84+
* ClassMode#BEFORE_EACH_TEST_METHOD BEFORE_EACH_TEST_METHOD}, the
85+
* {@linkplain ApplicationContext application context} of the test context
86+
* will be {@linkplain TestContext#markApplicationContextDirty marked as dirty} and the
87+
* {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE
88+
* REINJECT_DEPENDENCIES_ATTRIBUTE} in the test context will be set to {@code true}.
89+
* @since 4.2
90+
*/
91+
@Override
92+
public void beforeTestMethod(TestContext testContext) throws Exception {
93+
beforeOrAfterTestMethod(testContext, "Before", BEFORE_METHOD, BEFORE_EACH_TEST_METHOD);
94+
}
95+
96+
/**
97+
* If the current test method of the supplied {@linkplain TestContext test
98+
* context} is annotated with {@code @DirtiesContext} and the {@linkplain
99+
* DirtiesContext#methodMode() method mode} is set to {@link
100+
* MethodMode#AFTER_METHOD AFTER_METHOD}, or if the test class is
101+
* annotated with {@code @DirtiesContext} and the {@linkplain
102+
* DirtiesContext#classMode() class mode} is set to {@link
103+
* ClassMode#AFTER_EACH_TEST_METHOD AFTER_EACH_TEST_METHOD}, the
104+
* {@linkplain ApplicationContext application context} of the test context
105+
* will be {@linkplain TestContext#markApplicationContextDirty marked as dirty} and the
106+
* {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE
107+
* REINJECT_DEPENDENCIES_ATTRIBUTE} in the test context will be set to {@code true}.
70108
*/
71109
@Override
72110
public void afterTestMethod(TestContext testContext) throws Exception {
111+
beforeOrAfterTestMethod(testContext, "After", AFTER_METHOD, AFTER_EACH_TEST_METHOD);
112+
}
113+
114+
/**
115+
* If the test class of the supplied {@linkplain TestContext test context}
116+
* is annotated with {@code @DirtiesContext} and the {@linkplain
117+
* DirtiesContext#classMode() class mode} is set to {@link
118+
* ClassMode#AFTER_CLASS AFTER_CLASS}, the {@linkplain ApplicationContext
119+
* application context} of the test context will be
120+
* {@linkplain TestContext#markApplicationContextDirty marked as dirty}, and the
121+
* {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE
122+
* REINJECT_DEPENDENCIES_ATTRIBUTE} in the test context will be set to
123+
* {@code true}.
124+
*/
125+
@Override
126+
public void afterTestClass(TestContext testContext) throws Exception {
127+
beforeOrAfterTestClass(testContext, "After", AFTER_CLASS);
128+
}
129+
130+
/**
131+
* Marks the {@linkplain ApplicationContext application context} of the supplied
132+
* {@linkplain TestContext test context} as
133+
* {@linkplain TestContext#markApplicationContextDirty(DirtiesContext.HierarchyMode) dirty}
134+
* and sets {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE
135+
* REINJECT_DEPENDENCIES_ATTRIBUTE} in the test context to {@code true}.
136+
* @param testContext the test context whose application context should
137+
* marked as dirty
138+
* @param hierarchyMode the context cache clearing mode to be applied if the
139+
* context is part of a hierarchy; may be {@code null}
140+
* @since 3.2.2
141+
*/
142+
protected void dirtyContext(TestContext testContext, HierarchyMode hierarchyMode) {
143+
testContext.markApplicationContextDirty(hierarchyMode);
144+
testContext.setAttribute(DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE, Boolean.TRUE);
145+
}
146+
147+
/**
148+
* Perform the actual work for {@link #beforeTestMethod} and {@link #afterTestMethod}.
149+
* @since 4.2
150+
*/
151+
private void beforeOrAfterTestMethod(TestContext testContext, String phase, MethodMode requiredMethodMode,
152+
ClassMode requiredClassMode) throws Exception {
73153
Class<?> testClass = testContext.getTestClass();
74154
Assert.notNull(testClass, "The test class of the supplied TestContext must not be null");
75155
Method testMethod = testContext.getTestMethod();
@@ -78,67 +158,48 @@ public void afterTestMethod(TestContext testContext) throws Exception {
78158
final String annotationType = DirtiesContext.class.getName();
79159
AnnotationAttributes methodAnnAttrs = AnnotatedElementUtils.getAnnotationAttributes(testMethod, annotationType);
80160
AnnotationAttributes classAnnAttrs = AnnotatedElementUtils.getAnnotationAttributes(testClass, annotationType);
81-
boolean methodDirtiesContext = methodAnnAttrs != null;
82-
boolean classDirtiesContext = classAnnAttrs != null;
83-
ClassMode classMode = classDirtiesContext ? classAnnAttrs.<ClassMode> getEnum("classMode") : null;
161+
boolean methodAnnotated = methodAnnAttrs != null;
162+
boolean classAnnotated = classAnnAttrs != null;
163+
MethodMode methodMode = methodAnnotated ? methodAnnAttrs.<MethodMode> getEnum("methodMode") : null;
164+
ClassMode classMode = classAnnotated ? classAnnAttrs.<ClassMode> getEnum("classMode") : null;
84165

85166
if (logger.isDebugEnabled()) {
86167
logger.debug(String.format(
87-
"After test method: context %s, class dirties context [%s], class mode [%s], method dirties context [%s].",
88-
testContext, classDirtiesContext, classMode, methodDirtiesContext));
168+
"%s test method: context %s, class annotated with @DirtiesContext [%s] with mode [%s], method annotated with @DirtiesContext [%s] with mode [%s].",
169+
phase, testContext, classAnnotated, classMode, methodAnnotated, methodMode));
89170
}
90171

91-
if (methodDirtiesContext || (classMode == AFTER_EACH_TEST_METHOD)) {
92-
HierarchyMode hierarchyMode = methodDirtiesContext ? methodAnnAttrs.<HierarchyMode> getEnum("hierarchyMode")
172+
if ((methodMode == requiredMethodMode) || (classMode == requiredClassMode)) {
173+
HierarchyMode hierarchyMode = methodAnnotated ? methodAnnAttrs.<HierarchyMode> getEnum("hierarchyMode")
93174
: classAnnAttrs.<HierarchyMode> getEnum("hierarchyMode");
94175
dirtyContext(testContext, hierarchyMode);
95176
}
96177
}
97178

98179
/**
99-
* If the test class of the supplied {@linkplain TestContext test context} is
100-
* annotated with {@link DirtiesContext &#064;DirtiesContext}, the
101-
* {@linkplain ApplicationContext application context} of the test context will
102-
* be {@linkplain TestContext#markApplicationContextDirty marked as dirty},
103-
* and the
104-
* {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE
105-
* REINJECT_DEPENDENCIES_ATTRIBUTE} in the test context will be set to
106-
* {@code true}.
180+
* Perform the actual work for {@link #beforeTestClass} and {@link #afterTestClass}.
181+
* @since 4.2
107182
*/
108-
@Override
109-
public void afterTestClass(TestContext testContext) throws Exception {
183+
private void beforeOrAfterTestClass(TestContext testContext, String phase, ClassMode requiredClassMode)
184+
throws Exception {
110185
Class<?> testClass = testContext.getTestClass();
111186
Assert.notNull(testClass, "The test class of the supplied TestContext must not be null");
112187

113188
final String annotationType = DirtiesContext.class.getName();
114-
AnnotationAttributes annAttrs = AnnotatedElementUtils.getAnnotationAttributes(testClass, annotationType);
115-
boolean dirtiesContext = annAttrs != null;
189+
AnnotationAttributes classAnnAttrs = AnnotatedElementUtils.getAnnotationAttributes(testClass, annotationType);
190+
boolean classAnnotated = classAnnAttrs != null;
191+
ClassMode classMode = classAnnotated ? classAnnAttrs.<ClassMode> getEnum("classMode") : null;
116192

117193
if (logger.isDebugEnabled()) {
118-
logger.debug(String.format("After test class: context %s, dirtiesContext [%s].", testContext,
119-
dirtiesContext));
194+
logger.debug(String.format(
195+
"%s test class: context %s, class annotated with @DirtiesContext [%s] with mode [%s].", phase,
196+
testContext, classAnnotated, classMode));
120197
}
121-
if (dirtiesContext) {
122-
HierarchyMode hierarchyMode = annAttrs.<HierarchyMode> getEnum("hierarchyMode");
198+
199+
if (classMode == requiredClassMode) {
200+
HierarchyMode hierarchyMode = classAnnAttrs.<HierarchyMode> getEnum("hierarchyMode");
123201
dirtyContext(testContext, hierarchyMode);
124202
}
125203
}
126204

127-
/**
128-
* Marks the {@linkplain ApplicationContext application context} of the supplied
129-
* {@linkplain TestContext test context} as
130-
* {@linkplain TestContext#markApplicationContextDirty(DirtiesContext.HierarchyMode) dirty}
131-
* and sets {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE}
132-
* in the test context to {@code true}.
133-
* @param testContext the test context whose application context should
134-
* marked as dirty
135-
* @param hierarchyMode the context cache clearing mode to be applied if the
136-
* context is part of a hierarchy; may be {@code null}
137-
* @since 3.2.2
138-
*/
139-
protected void dirtyContext(TestContext testContext, HierarchyMode hierarchyMode) {
140-
testContext.markApplicationContextDirty(hierarchyMode);
141-
testContext.setAttribute(DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE, Boolean.TRUE);
142-
}
143-
144205
}

0 commit comments

Comments
 (0)