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