diff --git a/org.springframework.test/src/main/java/org/springframework/test/annotation/DirtiesContext.java b/org.springframework.test/src/main/java/org/springframework/test/annotation/DirtiesContext.java
index 5c8a66cb23c..ea9ae8d8225 100644
--- a/org.springframework.test/src/main/java/org/springframework/test/annotation/DirtiesContext.java
+++ b/org.springframework.test/src/main/java/org/springframework/test/annotation/DirtiesContext.java
@@ -27,8 +27,12 @@ import java.lang.annotation.Target;
* {@link org.springframework.context.ApplicationContext ApplicationContext}
* associated with a test is dirty and should be closed:
*
- * - after the current test, when declared at the method level, or
- * - after the current test class, when declared at the class level.
+ * - after the current test, when declared at the method level
+ * - after each test method in the current test class, when declared at the
+ * class level with class mode set to {@link ClassMode#AFTER_EACH_TEST_METHOD
+ * AFTER_EACH_TEST_METHOD}
+ * - after the current test class, when declared at the class level with class
+ * mode set to {@link ClassMode#AFTER_CLASS AFTER_CLASS}
*
*
* Use this annotation if a test has modified the context (for example, by
@@ -39,11 +43,14 @@ import java.lang.annotation.Target;
* @DirtiesContext may be used as a class-level and
* method-level annotation within the same class. In such scenarios, the
* ApplicationContext will be marked as dirty after any
- * such annotated method as well as after the entire class.
+ * such annotated method as well as after the entire class. If the
+ * {@link ClassMode} is set to {@link ClassMode#AFTER_EACH_TEST_METHOD
+ * AFTER_EACH_TEST_METHOD}, the context will be marked dirty after each test
+ * method in the class.
*
*
- * @author Rod Johnson
* @author Sam Brannen
+ * @author Rod Johnson
* @since 2.0
*/
@Target( { ElementType.TYPE, ElementType.METHOD })
@@ -51,4 +58,42 @@ import java.lang.annotation.Target;
@Documented
public @interface DirtiesContext {
+ /**
+ * Defines modes which determine how
+ * @DirtiesContext is interpreted when used to annotate a
+ * test class.
+ *
+ * @author Sam Brannen
+ * @since 3.0
+ */
+ public static enum ClassMode {
+
+ /**
+ * The associated ApplicationContext will be marked as
+ * dirty after the test class.
+ */
+ AFTER_CLASS,
+
+ /**
+ * The associated ApplicationContext will be marked as
+ * dirty after each test method in the class.
+ */
+ AFTER_EACH_TEST_METHOD;
+ }
+
+
+ /**
+ * The mode to use when a test class is annotated with
+ * @DirtiesContext.
+ *
+ * Defaults to {@link ClassMode#AFTER_CLASS AFTER_CLASS}.
+ *
+ *
+ * Note: setting the class mode on an annotated test method has no meaning,
+ * since the mere presence of the @DirtiesContext
+ * annotation on a test method is sufficient.
+ *
+ */
+ public ClassMode classMode() default ClassMode.AFTER_CLASS;
+
}
diff --git a/org.springframework.test/src/main/java/org/springframework/test/context/support/DirtiesContextTestExecutionListener.java b/org.springframework.test/src/main/java/org/springframework/test/context/support/DirtiesContextTestExecutionListener.java
index 98b00eca436..01056e69a6d 100644
--- a/org.springframework.test/src/main/java/org/springframework/test/context/support/DirtiesContextTestExecutionListener.java
+++ b/org.springframework.test/src/main/java/org/springframework/test/context/support/DirtiesContextTestExecutionListener.java
@@ -22,13 +22,15 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.test.context.TestContext;
import org.springframework.util.Assert;
/**
- * TestExecutionListener which processes test classes and test
- * methods configured with the {@link DirtiesContext @DirtiesContext}
- * annotation.
+ * TestExecutionListener which provides support for marking the
+ * ApplicationContext associated with a test as dirty for
+ * both test classes and test methods configured with the {@link DirtiesContext
+ * @DirtiesContext} annotation.
*
* @author Sam Brannen
* @author Juergen Hoeller
@@ -56,23 +58,37 @@ public class DirtiesContextTestExecutionListener extends AbstractTestExecutionLi
/**
* If the current test method of the supplied {@link TestContext test
* context} is annotated with {@link DirtiesContext @DirtiesContext},
- * the {@link ApplicationContext application context} of the test context
- * will be {@link TestContext#markApplicationContextDirty() marked as dirty}
- * , and the
+ * or if the test class is annotated with {@link DirtiesContext
+ * @DirtiesContext} and the {@link DirtiesContext#classMode() class
+ * mode} is set to {@link ClassMode#AFTER_EACH_TEST_METHOD
+ * AFTER_EACH_TEST_METHOD}, the {@link ApplicationContext application
+ * context} of the test context will be
+ * {@link TestContext#markApplicationContextDirty() marked as dirty} and the
* {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE
* REINJECT_DEPENDENCIES_ATTRIBUTE} in the test context will be set to
* true.
*/
@Override
public void afterTestMethod(TestContext testContext) throws Exception {
+ Class> testClass = testContext.getTestClass();
+ Assert.notNull(testClass, "The test class of the supplied TestContext must not be null");
Method testMethod = testContext.getTestMethod();
Assert.notNull(testMethod, "The test method of the supplied TestContext must not be null");
- boolean dirtiesContext = testMethod.isAnnotationPresent(DirtiesContext.class);
+ final Class annotationType = DirtiesContext.class;
+
+ boolean methodDirtiesContext = testMethod.isAnnotationPresent(annotationType);
+ boolean classDirtiesContext = testClass.isAnnotationPresent(annotationType);
+ DirtiesContext classDirtiesContextAnnotation = testClass.getAnnotation(annotationType);
+ ClassMode classMode = classDirtiesContext ? classDirtiesContextAnnotation.classMode() : null;
+
if (logger.isDebugEnabled()) {
- logger.debug("After test method: context [" + testContext + "], dirtiesContext [" + dirtiesContext + "].");
+ logger.debug("After test method: context [" + testContext + "], class-level dirtiesContext ["
+ + classDirtiesContext + "], class mode [" + classMode + "], method-level dirtiesContext ["
+ + methodDirtiesContext + "].");
}
- if (dirtiesContext) {
+
+ if (methodDirtiesContext || (classDirtiesContext && classMode == ClassMode.AFTER_EACH_TEST_METHOD)) {
dirtyContext(testContext);
}
}
diff --git a/org.springframework.test/src/test/java/org/springframework/test/context/ClassLevelDirtiesContextTests.java b/org.springframework.test/src/test/java/org/springframework/test/context/ClassLevelDirtiesContextTests.java
index ecb53d3df30..58edfc3ee56 100644
--- a/org.springframework.test/src/test/java/org/springframework/test/context/ClassLevelDirtiesContextTests.java
+++ b/org.springframework.test/src/test/java/org/springframework/test/context/ClassLevelDirtiesContextTests.java
@@ -19,6 +19,8 @@ package org.springframework.test.context;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import java.util.concurrent.atomic.AtomicInteger;
+
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -28,6 +30,7 @@ import org.junit.runners.JUnit4;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit4.TrackingRunListener;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
@@ -45,6 +48,10 @@ import org.springframework.test.context.support.DirtiesContextTestExecutionListe
@RunWith(JUnit4.class)
public class ClassLevelDirtiesContextTests {
+ private static final AtomicInteger cacheHits = new AtomicInteger(0);
+ private static final AtomicInteger cacheMisses = new AtomicInteger(0);
+
+
/**
* Asserts the statistics of the supplied context cache.
*
@@ -53,8 +60,8 @@ public class ClassLevelDirtiesContextTests {
* @param expectedHitCount the expected hit count
* @param expectedMissCount the expected miss count
*/
- private static final void assertContextCacheStatistics(String usageScenario, int expectedSize,
- int expectedHitCount, int expectedMissCount) {
+ private static final void assertCacheStats(String usageScenario, int expectedSize, int expectedHitCount,
+ int expectedMissCount) {
ContextCache contextCache = TestContextManager.contextCache;
assertEquals("Verifying number of contexts in cache (" + usageScenario + ").", expectedSize,
@@ -65,10 +72,10 @@ public class ClassLevelDirtiesContextTests {
contextCache.getMissCount());
}
- private static final void runTestClassAndAssertRunListenerStats(Class> testClass) {
+ private static final void runTestClassAndAssertStats(Class> testClass, int expectedTestCount) {
final int expectedTestFailureCount = 0;
- final int expectedTestStartedCount = 1;
- final int expectedTestFinishedCount = 1;
+ final int expectedTestStartedCount = expectedTestCount;
+ final int expectedTestFinishedCount = expectedTestCount;
TrackingRunListener listener = new TrackingRunListener();
JUnitCore jUnitCore = new JUnitCore();
@@ -88,45 +95,63 @@ public class ClassLevelDirtiesContextTests {
ContextCache contextCache = TestContextManager.contextCache;
contextCache.clear();
contextCache.clearStatistics();
- assertContextCacheStatistics("BeforeClass", 0, 0, 0);
- }
-
- @AfterClass
- public static void verifyFinalCacheState() {
- assertContextCacheStatistics("AfterClass", 0, 3, 5);
+ assertCacheStats("BeforeClass", 0, cacheHits.get(), cacheMisses.get());
}
@Test
public void verifyDirtiesContextBehavior() throws Exception {
- int hits = 0;
- int misses = 0;
+ runTestClassAndAssertStats(CleanTestCase.class, 1);
+ assertCacheStats("after clean test class", 1, cacheHits.get(), cacheMisses.incrementAndGet());
- runTestClassAndAssertRunListenerStats(CleanTestCase.class);
- assertContextCacheStatistics("after clean test class", 1, hits, ++misses);
+ runTestClassAndAssertStats(ClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase.class, 1);
+ assertCacheStats("after class-level @DirtiesContext with clean test method and default class mode", 0,
+ cacheHits.incrementAndGet(), cacheMisses.get());
- runTestClassAndAssertRunListenerStats(ClassLevelDirtiesContextWithCleanMethodsTestCase.class);
- assertContextCacheStatistics("after class-level @DirtiesContext with clean test method", 0, ++hits, misses);
+ runTestClassAndAssertStats(CleanTestCase.class, 1);
+ assertCacheStats("after clean test class", 1, cacheHits.get(), cacheMisses.incrementAndGet());
- runTestClassAndAssertRunListenerStats(CleanTestCase.class);
- assertContextCacheStatistics("after clean test class", 1, hits, ++misses);
+ runTestClassAndAssertStats(ClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase.class, 1);
+ assertCacheStats("after class-level @DirtiesContext with clean test method and AFTER_CLASS mode", 0,
+ cacheHits.incrementAndGet(), cacheMisses.get());
- runTestClassAndAssertRunListenerStats(ClassLevelDirtiesContextWithDirtyMethodsTestCase.class);
- assertContextCacheStatistics("after class-level @DirtiesContext with dirty test method", 0, ++hits, misses);
+ runTestClassAndAssertStats(CleanTestCase.class, 1);
+ assertCacheStats("after clean test class", 1, cacheHits.get(), cacheMisses.incrementAndGet());
- runTestClassAndAssertRunListenerStats(ClassLevelDirtiesContextWithDirtyMethodsTestCase.class);
- assertContextCacheStatistics("after class-level @DirtiesContext with dirty test method", 0, hits, ++misses);
+ runTestClassAndAssertStats(ClassLevelDirtiesContextWithAfterEachTestMethodModeTestCase.class, 3);
+ assertCacheStats("after class-level @DirtiesContext with clean test method and AFTER_EACH_TEST_METHOD mode", 0,
+ cacheHits.incrementAndGet(), cacheMisses.addAndGet(2));
- runTestClassAndAssertRunListenerStats(ClassLevelDirtiesContextWithDirtyMethodsTestCase.class);
- assertContextCacheStatistics("after class-level @DirtiesContext with dirty test method", 0, hits, ++misses);
+ runTestClassAndAssertStats(CleanTestCase.class, 1);
+ assertCacheStats("after clean test class", 1, cacheHits.get(), cacheMisses.incrementAndGet());
- runTestClassAndAssertRunListenerStats(CleanTestCase.class);
- assertContextCacheStatistics("after clean test class", 1, hits, ++misses);
+ runTestClassAndAssertStats(ClassLevelDirtiesContextWithDirtyMethodsTestCase.class, 1);
+ assertCacheStats("after class-level @DirtiesContext with dirty test method", 0, cacheHits.incrementAndGet(),
+ cacheMisses.get());
- runTestClassAndAssertRunListenerStats(ClassLevelDirtiesContextWithCleanMethodsTestCase.class);
- assertContextCacheStatistics("after class-level @DirtiesContext with clean test method", 0, ++hits, misses);
+ runTestClassAndAssertStats(ClassLevelDirtiesContextWithDirtyMethodsTestCase.class, 1);
+ assertCacheStats("after class-level @DirtiesContext with dirty test method", 0, cacheHits.get(),
+ cacheMisses.incrementAndGet());
+
+ runTestClassAndAssertStats(ClassLevelDirtiesContextWithDirtyMethodsTestCase.class, 1);
+ assertCacheStats("after class-level @DirtiesContext with dirty test method", 0, cacheHits.get(),
+ cacheMisses.incrementAndGet());
+
+ runTestClassAndAssertStats(CleanTestCase.class, 1);
+ assertCacheStats("after clean test class", 1, cacheHits.get(), cacheMisses.incrementAndGet());
+
+ runTestClassAndAssertStats(ClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase.class, 1);
+ assertCacheStats("after class-level @DirtiesContext with clean test method and AFTER_CLASS mode", 0,
+ cacheHits.incrementAndGet(), cacheMisses.get());
}
+ @AfterClass
+ public static void verifyFinalCacheState() {
+ assertCacheStats("AfterClass", 0, cacheHits.get(), cacheMisses.get());
+ }
+
+
+ // -------------------------------------------------------------------
@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners( { DependencyInjectionTestExecutionListener.class,
@@ -153,7 +178,7 @@ public class ClassLevelDirtiesContextTests {
}
@DirtiesContext
- public static final class ClassLevelDirtiesContextWithCleanMethodsTestCase extends BaseTestCase {
+ public static final class ClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase extends BaseTestCase {
@Test
public void verifyContextWasAutowired() {
@@ -161,6 +186,34 @@ public class ClassLevelDirtiesContextTests {
}
}
+ @DirtiesContext(classMode = ClassMode.AFTER_CLASS)
+ public static final class ClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase extends BaseTestCase {
+
+ @Test
+ public void verifyContextWasAutowired() {
+ assertApplicationContextWasAutowired();
+ }
+ }
+
+ @DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
+ public static final class ClassLevelDirtiesContextWithAfterEachTestMethodModeTestCase extends BaseTestCase {
+
+ @Test
+ public void verifyContextWasAutowired1() {
+ assertApplicationContextWasAutowired();
+ }
+
+ @Test
+ public void verifyContextWasAutowired2() {
+ assertApplicationContextWasAutowired();
+ }
+
+ @Test
+ public void verifyContextWasAutowired3() {
+ assertApplicationContextWasAutowired();
+ }
+ }
+
@DirtiesContext
public static final class ClassLevelDirtiesContextWithDirtyMethodsTestCase extends BaseTestCase {