Skip to content

Commit 8dec6af

Browse files
committed
[SPR-4702] Added support for @DirtiesContext at the test class level.
1 parent 0222a4a commit 8dec6af

17 files changed

+713
-134
lines changed

Diff for: org.springframework.test/src/main/java/org/springframework/test/annotation/DirtiesContext.java

+20-4
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,30 @@
2323
import java.lang.annotation.Target;
2424

2525
/**
26-
* Test annotation to indicate that a test method <em>dirties</em> the context
27-
* for the current test.
28-
*
26+
* Test annotation which indicates that the
27+
* {@link org.springframework.context.ApplicationContext ApplicationContext}
28+
* associated with a test is <em>dirty</em> and should be closed:
29+
* <ul>
30+
* <li>after the current test, when declared at the method level, or</li>
31+
* <li>after the current test class, when declared at the class level.</li>
32+
* </ul>
33+
* <p>
34+
* Use this annotation if a test has modified the context (for example, by
35+
* replacing a bean definition). Subsequent tests will be supplied a new
36+
* context.
37+
* </p>
38+
* <p>
39+
* <code>&#064;DirtiesContext</code> may be used as a class-level and
40+
* method-level annotation within the same class. In such scenarios, the
41+
* <code>ApplicationContext</code> will be marked as <em>dirty</em> after any
42+
* such annotated method as well as after the entire class.
43+
* </p>
44+
*
2945
* @author Rod Johnson
3046
* @author Sam Brannen
3147
* @since 2.0
3248
*/
33-
@Target({ElementType.METHOD})
49+
@Target( { ElementType.TYPE, ElementType.METHOD })
3450
@Retention(RetentionPolicy.RUNTIME)
3551
@Documented
3652
public @interface DirtiesContext {

Diff for: org.springframework.test/src/main/java/org/springframework/test/context/TestContextManager.java

+82-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2008 the original author or authors.
2+
* Copyright 2002-2009 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.
@@ -236,6 +236,39 @@ protected Set<Class<? extends TestExecutionListener>> getDefaultTestExecutionLis
236236
return defaultListenerClasses;
237237
}
238238

239+
/**
240+
* Hook for pre-processing a test class <em>before</em> execution of any
241+
* tests within the class. Should be called prior to any framework-specific
242+
* <em>before class methods</em> (e.g., methods annotated with JUnit's
243+
* {@link org.junit.BeforeClass &#064;BeforeClass}).
244+
* <p>
245+
* An attempt will be made to give each registered
246+
* {@link TestExecutionListener} a chance to pre-process the test class
247+
* execution. If a listener throws an exception, however, the remaining
248+
* registered listeners will <strong>not</strong> be called.
249+
*
250+
* @throws Exception if a registered TestExecutionListener throws an
251+
* exception
252+
* @see #getTestExecutionListeners()
253+
*/
254+
public void beforeTestClass() throws Exception {
255+
final Class<?> testClass = getTestContext().getTestClass();
256+
if (logger.isTraceEnabled()) {
257+
logger.trace("beforeTestClass(): class [" + testClass + "]");
258+
}
259+
260+
for (TestExecutionListener testExecutionListener : getTestExecutionListeners()) {
261+
try {
262+
testExecutionListener.beforeTestClass(getTestContext());
263+
}
264+
catch (Exception ex) {
265+
logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener
266+
+ "] to process 'before class' callback for test class [" + testClass + "]", ex);
267+
throw ex;
268+
}
269+
}
270+
}
271+
239272
/**
240273
* Hook for preparing a test instance prior to execution of any individual
241274
* test methods, for example for injecting dependencies, etc. Should be
@@ -279,7 +312,7 @@ public void prepareTestInstance(Object testInstance) throws Exception {
279312
* {@link Method test method}, for example for setting up test fixtures,
280313
* starting a transaction, etc. Should be called prior to any
281314
* framework-specific <em>before methods</em> (e.g., methods annotated with
282-
* JUnit's {@link org.junit.Before &#064;Before} ).
315+
* JUnit's {@link org.junit.Before &#064;Before}).
283316
* <p>
284317
* The managed {@link TestContext} will be updated with the supplied
285318
* <code>testInstance</code> and <code>testMethod</code>.
@@ -377,4 +410,51 @@ public void afterTestMethod(Object testInstance, Method testMethod, Throwable ex
377410
}
378411
}
379412

413+
/**
414+
* Hook for post-processing a test class <em>after</em> execution of all
415+
* tests within the class. Should be called after any framework-specific
416+
* <em>after class methods</em> (e.g., methods annotated with JUnit's
417+
* {@link org.junit.AfterClass &#064;AfterClass}).
418+
* <p>
419+
* Each registered {@link TestExecutionListener} will be given a chance to
420+
* post-process the test class. If a listener throws an exception, the
421+
* remaining registered listeners will still be called, but the first
422+
* exception thrown will be tracked and rethrown after all listeners have
423+
* executed. Note that registered listeners will be executed in the opposite
424+
* order in which they were registered.
425+
*
426+
* @throws Exception if a registered TestExecutionListener throws an
427+
* exception
428+
* @see #getTestExecutionListeners()
429+
*/
430+
public void afterTestClass() throws Exception {
431+
final Class<?> testClass = getTestContext().getTestClass();
432+
if (logger.isTraceEnabled()) {
433+
logger.trace("afterTestClass(): class [" + testClass + "]");
434+
}
435+
436+
// Traverse the TestExecutionListeners in reverse order to ensure proper
437+
// "wrapper"-style execution of listeners.
438+
List<TestExecutionListener> listenersReversed = new ArrayList<TestExecutionListener>(
439+
getTestExecutionListeners());
440+
Collections.reverse(listenersReversed);
441+
442+
Exception afterTestClassException = null;
443+
for (TestExecutionListener testExecutionListener : listenersReversed) {
444+
try {
445+
testExecutionListener.afterTestClass(getTestContext());
446+
}
447+
catch (Exception ex) {
448+
logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener
449+
+ "] to process 'after class' callback for test class [" + testClass + "]", ex);
450+
if (afterTestClassException == null) {
451+
afterTestClassException = ex;
452+
}
453+
}
454+
}
455+
if (afterTestClassException != null) {
456+
throw afterTestClassException;
457+
}
458+
}
459+
380460
}

Diff for: org.springframework.test/src/main/java/org/springframework/test/context/TestExecutionListener.java

+50-16
Original file line numberDiff line numberDiff line change
@@ -31,25 +31,43 @@
3131
* Spring provides the following out-of-the-box implementations:
3232
* </p>
3333
* <ul>
34-
* <li>{@link org.springframework.test.context.support.DependencyInjectionTestExecutionListener DependencyInjectionTestExecutionListener}</li>
35-
* <li>{@link org.springframework.test.context.support.DirtiesContextTestExecutionListener DirtiesContextTestExecutionListener}</li>
36-
* <li>{@link org.springframework.test.context.transaction.TransactionalTestExecutionListener TransactionalTestExecutionListener}</li>
34+
* <li>
35+
* {@link org.springframework.test.context.support.DependencyInjectionTestExecutionListener
36+
* DependencyInjectionTestExecutionListener}</li>
37+
* <li>
38+
* {@link org.springframework.test.context.support.DirtiesContextTestExecutionListener
39+
* DirtiesContextTestExecutionListener}</li>
40+
* <li>
41+
* {@link org.springframework.test.context.transaction.TransactionalTestExecutionListener
42+
* TransactionalTestExecutionListener}</li>
3743
* </ul>
38-
*
44+
*
3945
* @author Sam Brannen
4046
* @author Juergen Hoeller
4147
* @since 2.5
4248
*/
4349
public interface TestExecutionListener {
4450

51+
/**
52+
* Pre-processes a test class <em>before</em> execution of all tests within
53+
* the class.
54+
* <p>
55+
* This method should be called immediately before framework-specific
56+
* <em>before class</em> lifecycle callbacks.
57+
*
58+
* @param testContext the test context for the test; never <code>null</code>
59+
* @throws Exception allows any exception to propagate
60+
*/
61+
void beforeTestClass(TestContext testContext) throws Exception;
62+
4563
/**
4664
* Prepares the {@link Object test instance} of the supplied
47-
* {@link TestContext test context}, for example by injecting
48-
* dependencies.
49-
* <p>This method should be called immediately after instantiation
50-
* of the test instance but prior to any framework-specific lifecycle
51-
* callbacks.
52-
* @param testContext the test context for the test (never <code>null</code>)
65+
* {@link TestContext test context}, for example by injecting dependencies.
66+
* <p>
67+
* This method should be called immediately after instantiation of the test
68+
* instance but prior to any framework-specific lifecycle callbacks.
69+
*
70+
* @param testContext the test context for the test; never <code>null</code>
5371
* @throws Exception allows any exception to propagate
5472
*/
5573
void prepareTestInstance(TestContext testContext) throws Exception;
@@ -59,10 +77,12 @@ public interface TestExecutionListener {
5977
* {@link java.lang.reflect.Method test method} in the supplied
6078
* {@link TestContext test context}, for example by setting up test
6179
* fixtures.
62-
* <p>This method should be called immediately prior to any
63-
* framework-specific <em>before</em> lifecycle callbacks.
80+
* <p>
81+
* This method should be called immediately prior to framework-specific
82+
* <em>before</em> lifecycle callbacks.
83+
*
6484
* @param testContext the test context in which the test method will be
65-
* executed (never <code>null</code>)
85+
* executed; never <code>null</code>
6686
* @throws Exception allows any exception to propagate
6787
*/
6888
void beforeTestMethod(TestContext testContext) throws Exception;
@@ -72,12 +92,26 @@ public interface TestExecutionListener {
7292
* {@link java.lang.reflect.Method test method} in the supplied
7393
* {@link TestContext test context}, for example by tearing down test
7494
* fixtures.
75-
* <p>This method should be called immediately after any
76-
* framework-specific <em>after</em> lifecycle callbacks.
95+
* <p>
96+
* This method should be called immediately after framework-specific
97+
* <em>after</em> lifecycle callbacks.
98+
*
7799
* @param testContext the test context in which the test method was
78-
* executed (never <code>null</code>)
100+
* executed; never <code>null</code>
79101
* @throws Exception allows any exception to propagate
80102
*/
81103
void afterTestMethod(TestContext testContext) throws Exception;
82104

105+
/**
106+
* Post-processes a test class <em>after</em> execution of all tests within
107+
* the class.
108+
* <p>
109+
* This method should be called immediately after framework-specific
110+
* <em>after class</em> lifecycle callbacks.
111+
*
112+
* @param testContext the test context for the test; never <code>null</code>
113+
* @throws Exception allows any exception to propagate
114+
*/
115+
void afterTestClass(TestContext testContext) throws Exception;
116+
83117
}

Diff for: org.springframework.test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java

+53-23
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@
3939
import org.springframework.test.annotation.Repeat;
4040
import org.springframework.test.annotation.Timed;
4141
import org.springframework.test.context.TestContextManager;
42-
import org.springframework.test.context.junit4.statements.RunSpringTestContextAfters;
43-
import org.springframework.test.context.junit4.statements.RunSpringTestContextBefores;
42+
import org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks;
43+
import org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks;
44+
import org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks;
45+
import org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks;
4446
import org.springframework.test.context.junit4.statements.SpringFailOnTimeout;
4547
import org.springframework.test.context.junit4.statements.SpringRepeat;
4648

@@ -142,20 +144,6 @@ protected String getDefaultContextLoaderClassName(Class<?> clazz) {
142144
return null;
143145
}
144146

145-
/**
146-
* Delegates to the parent implementation for creating the test instance and
147-
* then allows the {@link #getTestContextManager() TestContextManager} to
148-
* prepare the test instance before returning it.
149-
*
150-
* @see TestContextManager#prepareTestInstance(Object)
151-
*/
152-
@Override
153-
protected Object createTest() throws Exception {
154-
Object testInstance = super.createTest();
155-
getTestContextManager().prepareTestInstance(testInstance);
156-
return testInstance;
157-
}
158-
159147
/**
160148
* Returns a description suitable for an ignored test class if the test is
161149
* disabled via <code>&#064;IfProfileValue</code> at the class-level, and
@@ -191,6 +179,47 @@ public void run(RunNotifier notifier) {
191179
super.run(notifier);
192180
}
193181

182+
/**
183+
* Wraps the {@link Statement} returned by the parent implementation with a
184+
* {@link RunBeforeTestClassCallbacks} statement, thus preserving the
185+
* default functionality but adding support for the Spring TestContext
186+
* Framework.
187+
*
188+
* @see RunBeforeTestClassCallbacks
189+
*/
190+
@Override
191+
protected Statement withBeforeClasses(Statement statement) {
192+
Statement junitBeforeClasses = super.withBeforeClasses(statement);
193+
return new RunBeforeTestClassCallbacks(junitBeforeClasses, getTestContextManager());
194+
}
195+
196+
/**
197+
* Wraps the {@link Statement} returned by the parent implementation with a
198+
* {@link RunAfterTestClassCallbacks} statement, thus preserving the default
199+
* functionality but adding support for the Spring TestContext Framework.
200+
*
201+
* @see RunAfterTestClassCallbacks
202+
*/
203+
@Override
204+
protected Statement withAfterClasses(Statement statement) {
205+
Statement junitAfterClasses = super.withAfterClasses(statement);
206+
return new RunAfterTestClassCallbacks(junitAfterClasses, getTestContextManager());
207+
}
208+
209+
/**
210+
* Delegates to the parent implementation for creating the test instance and
211+
* then allows the {@link #getTestContextManager() TestContextManager} to
212+
* prepare the test instance before returning it.
213+
*
214+
* @see TestContextManager#prepareTestInstance(Object)
215+
*/
216+
@Override
217+
protected Object createTest() throws Exception {
218+
Object testInstance = super.createTest();
219+
getTestContextManager().prepareTestInstance(testInstance);
220+
return testInstance;
221+
}
222+
194223
/**
195224
* Performs the same logic as
196225
* {@link BlockJUnit4ClassRunner#runChild(FrameworkMethod, RunNotifier)},
@@ -393,30 +422,31 @@ protected long getSpringTimeout(FrameworkMethod frameworkMethod) {
393422

394423
/**
395424
* Wraps the {@link Statement} returned by the parent implementation with a
396-
* {@link RunSpringTestContextBefores} statement, thus preserving the
425+
* {@link RunBeforeTestMethodCallbacks} statement, thus preserving the
397426
* default functionality but adding support for the Spring TestContext
398427
* Framework.
399428
*
400-
* @see RunSpringTestContextBefores
429+
* @see RunBeforeTestMethodCallbacks
401430
*/
402431
@Override
403432
protected Statement withBefores(FrameworkMethod frameworkMethod, Object testInstance, Statement statement) {
404433
Statement junitBefores = super.withBefores(frameworkMethod, testInstance, statement);
405-
return new RunSpringTestContextBefores(junitBefores, testInstance, frameworkMethod.getMethod(),
434+
return new RunBeforeTestMethodCallbacks(junitBefores, testInstance, frameworkMethod.getMethod(),
406435
getTestContextManager());
407436
}
408437

409438
/**
410439
* Wraps the {@link Statement} returned by the parent implementation with a
411-
* {@link RunSpringTestContextAfters} statement, thus preserving the default
412-
* functionality but adding support for the Spring TestContext Framework.
440+
* {@link RunAfterTestMethodCallbacks} statement, thus preserving the
441+
* default functionality but adding support for the Spring TestContext
442+
* Framework.
413443
*
414-
* @see RunSpringTestContextAfters
444+
* @see RunAfterTestMethodCallbacks
415445
*/
416446
@Override
417447
protected Statement withAfters(FrameworkMethod frameworkMethod, Object testInstance, Statement statement) {
418448
Statement junitAfters = super.withAfters(frameworkMethod, testInstance, statement);
419-
return new RunSpringTestContextAfters(junitAfters, testInstance, frameworkMethod.getMethod(),
449+
return new RunAfterTestMethodCallbacks(junitAfters, testInstance, frameworkMethod.getMethod(),
420450
getTestContextManager());
421451
}
422452

0 commit comments

Comments
 (0)