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: * *

* 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 {