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
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,36 +26,49 @@ import java.lang.annotation.Target;
|
||||
/**
|
||||
* Test annotation which indicates that the
|
||||
* {@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 therefore be closed
|
||||
* and removed from the context cache.
|
||||
*
|
||||
* <ul>
|
||||
* <li>after the current test, when declared at the method 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>
|
||||
*
|
||||
* <p>Use this annotation if a test has modified the context — for example,
|
||||
* by replacing a bean definition or changing the state of a singleton bean.
|
||||
* Subsequent tests will be supplied a new context.
|
||||
* <p>Use this annotation if a test has modified the context — for
|
||||
* example, by modifying the state of a singleton bean, modifying the state
|
||||
* of an embedded database, etc. Subsequent tests that request the same
|
||||
* context will be supplied a new context.
|
||||
*
|
||||
* <p>{@code @DirtiesContext} may be used as a class-level and method-level
|
||||
* annotation within the same class. In such scenarios, the
|
||||
* {@code ApplicationContext} will be marked as <em>dirty</em> after any
|
||||
* 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.
|
||||
* annotation within the same class or class hierarchy. In such scenarios, the
|
||||
* {@code ApplicationContext} will be marked as <em>dirty</em> before or
|
||||
* after any such annotated method as well as before or after the current test
|
||||
* class, depending on the configured {@link #methodMode} and {@link #classMode}.
|
||||
*
|
||||
* <p>As of Spring Framework 4.0, this annotation may be used as a
|
||||
* <em>meta-annotation</em> to create custom <em>composed annotations</em>.
|
||||
*
|
||||
* <h3>Supported Test Phases</h3>
|
||||
* <ul>
|
||||
* <li><strong>Before current test class</strong>: when declared at the class
|
||||
* level with class mode set to {@link ClassMode#BEFORE_CLASS BEFORE_CLASS}</li>
|
||||
* <li><strong>Before each test method in current test class</strong>: when
|
||||
* declared at the class level with class mode set to
|
||||
* {@link ClassMode#BEFORE_EACH_TEST_METHOD BEFORE_EACH_TEST_METHOD}</li>
|
||||
* <li><strong>Before current test method</strong>: when declared at the
|
||||
* method level with method mode set to
|
||||
* {@link MethodMode#BEFORE_METHOD BEFORE_METHOD}</li>
|
||||
* <li><strong>After current test method</strong>: when declared at the
|
||||
* method level with method mode set to
|
||||
* {@link MethodMode#AFTER_METHOD AFTER_METHOD}</li>
|
||||
* <li><strong>After each test method in current test class</strong>: when
|
||||
* declared at the class level with class mode set to
|
||||
* {@link ClassMode#AFTER_EACH_TEST_METHOD AFTER_EACH_TEST_METHOD}</li>
|
||||
* <li><strong>After current test class</strong>: when declared at the
|
||||
* class level with class mode set to
|
||||
* {@link ClassMode#AFTER_CLASS AFTER_CLASS}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @author Rod Johnson
|
||||
* @since 2.0
|
||||
* @see org.springframework.test.context.ContextConfiguration
|
||||
* @see org.springframework.test.context.support.DirtiesContextTestExecutionListener
|
||||
*/
|
||||
@Documented
|
||||
@Inherited
|
||||
@@ -63,6 +76,27 @@ import java.lang.annotation.Target;
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
public @interface DirtiesContext {
|
||||
|
||||
/**
|
||||
* Defines <i>modes</i> which determine how {@code @DirtiesContext} is
|
||||
* interpreted when used to annotate a test method.
|
||||
*
|
||||
* @since 4.2
|
||||
*/
|
||||
static enum MethodMode {
|
||||
|
||||
/**
|
||||
* The associated {@code ApplicationContext} will be marked as
|
||||
* <em>dirty</em> before the corresponding test method.
|
||||
*/
|
||||
BEFORE_METHOD,
|
||||
|
||||
/**
|
||||
* The associated {@code ApplicationContext} will be marked as
|
||||
* <em>dirty</em> after the corresponding test method.
|
||||
*/
|
||||
AFTER_METHOD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines <i>modes</i> which determine how {@code @DirtiesContext} is
|
||||
* interpreted when used to annotate a test class.
|
||||
@@ -73,15 +107,31 @@ public @interface DirtiesContext {
|
||||
|
||||
/**
|
||||
* The associated {@code ApplicationContext} will be marked as
|
||||
* <em>dirty</em> after the test class.
|
||||
* <em>dirty</em> before the test class.
|
||||
*
|
||||
* @since 4.2
|
||||
*/
|
||||
AFTER_CLASS,
|
||||
BEFORE_CLASS,
|
||||
|
||||
/**
|
||||
* The associated {@code ApplicationContext} will be marked as
|
||||
* <em>dirty</em> before each test method in the class.
|
||||
*
|
||||
* @since 4.2
|
||||
*/
|
||||
BEFORE_EACH_TEST_METHOD,
|
||||
|
||||
/**
|
||||
* The associated {@code ApplicationContext} will be marked as
|
||||
* <em>dirty</em> after each test method in the class.
|
||||
*/
|
||||
AFTER_EACH_TEST_METHOD;
|
||||
AFTER_EACH_TEST_METHOD,
|
||||
|
||||
/**
|
||||
* The associated {@code ApplicationContext} will be marked as
|
||||
* <em>dirty</em> after the test class.
|
||||
*/
|
||||
AFTER_CLASS;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,13 +169,23 @@ public @interface DirtiesContext {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The <i>mode</i> to use when a test method is annotated with
|
||||
* {@code @DirtiesContext}.
|
||||
* <p>Defaults to {@link MethodMode#AFTER_METHOD AFTER_METHOD}.
|
||||
* <p>Setting the method mode on an annotated test class has no meaning.
|
||||
* For class-level control, use {@link #classMode} instead.
|
||||
*
|
||||
* @since 4.2
|
||||
*/
|
||||
MethodMode methodMode() default MethodMode.AFTER_METHOD;
|
||||
|
||||
/**
|
||||
* The <i>mode</i> to use when a test class is annotated with
|
||||
* {@code @DirtiesContext}.
|
||||
* <p>Defaults to {@link ClassMode#AFTER_CLASS AFTER_CLASS}.
|
||||
* <p>Note: Setting the class mode on an annotated test method has no meaning,
|
||||
* since the mere presence of the {@code @DirtiesContext} annotation on a
|
||||
* test method is sufficient.
|
||||
* <p>Setting the class mode on an annotated test method has no meaning.
|
||||
* For method-level control, use {@link #methodMode} instead.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -27,16 +27,18 @@ import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.annotation.DirtiesContext.ClassMode;
|
||||
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
|
||||
import org.springframework.test.annotation.DirtiesContext.MethodMode;
|
||||
import org.springframework.test.context.TestContext;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import static org.springframework.test.annotation.DirtiesContext.ClassMode.*;
|
||||
import static org.springframework.test.annotation.DirtiesContext.MethodMode.*;
|
||||
|
||||
/**
|
||||
* {@code TestExecutionListener} which provides support for marking the
|
||||
* {@code ApplicationContext} associated with a test as <em>dirty</em> for
|
||||
* both test classes and test methods configured with the {@link DirtiesContext
|
||||
* @DirtiesContext} annotation.
|
||||
* both test classes and test methods annotated with the
|
||||
* {@link DirtiesContext @DirtiesContext} annotation.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @author Juergen Hoeller
|
||||
@@ -57,79 +59,80 @@ public class DirtiesContextTestExecutionListener extends AbstractTestExecutionLi
|
||||
}
|
||||
|
||||
/**
|
||||
* If the current test method of the supplied {@linkplain TestContext test
|
||||
* context} is annotated with {@link DirtiesContext @DirtiesContext},
|
||||
* or if the test class is annotated with {@link DirtiesContext
|
||||
* @DirtiesContext} and the {@linkplain DirtiesContext#classMode() class
|
||||
* mode} is set to {@link ClassMode#AFTER_EACH_TEST_METHOD
|
||||
* AFTER_EACH_TEST_METHOD}, the {@linkplain ApplicationContext application
|
||||
* context} of the test context will be
|
||||
* {@linkplain TestContext#markApplicationContextDirty marked as dirty} and the
|
||||
* {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE}
|
||||
* in the test context will be set to {@code true}.
|
||||
* If the test class of the supplied {@linkplain TestContext test context}
|
||||
* is annotated with {@code @DirtiesContext} and the {@linkplain
|
||||
* DirtiesContext#classMode() class mode} is set to {@link
|
||||
* ClassMode#BEFORE_CLASS BEFORE_CLASS}, the {@linkplain ApplicationContext
|
||||
* application context} of the test context will be
|
||||
* {@linkplain TestContext#markApplicationContextDirty marked as dirty}, and the
|
||||
* {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE
|
||||
* REINJECT_DEPENDENCIES_ATTRIBUTE} in the test context will be set to
|
||||
* {@code 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");
|
||||
|
||||
final String annotationType = DirtiesContext.class.getName();
|
||||
AnnotationAttributes methodAnnAttrs = AnnotatedElementUtils.getAnnotationAttributes(testMethod, annotationType);
|
||||
AnnotationAttributes classAnnAttrs = AnnotatedElementUtils.getAnnotationAttributes(testClass, annotationType);
|
||||
boolean methodDirtiesContext = methodAnnAttrs != null;
|
||||
boolean classDirtiesContext = classAnnAttrs != null;
|
||||
ClassMode classMode = classDirtiesContext ? classAnnAttrs.<ClassMode> getEnum("classMode") : null;
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format(
|
||||
"After test method: context %s, class dirties context [%s], class mode [%s], method dirties context [%s].",
|
||||
testContext, classDirtiesContext, classMode, methodDirtiesContext));
|
||||
}
|
||||
|
||||
if (methodDirtiesContext || (classMode == AFTER_EACH_TEST_METHOD)) {
|
||||
HierarchyMode hierarchyMode = methodDirtiesContext ? methodAnnAttrs.<HierarchyMode> getEnum("hierarchyMode")
|
||||
: classAnnAttrs.<HierarchyMode> getEnum("hierarchyMode");
|
||||
dirtyContext(testContext, hierarchyMode);
|
||||
}
|
||||
public void beforeTestClass(TestContext testContext) throws Exception {
|
||||
beforeOrAfterTestClass(testContext, "Before", BEFORE_CLASS);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the test class of the supplied {@linkplain TestContext test context} is
|
||||
* annotated with {@link DirtiesContext @DirtiesContext}, the
|
||||
* {@linkplain ApplicationContext application context} of the test context will
|
||||
* be {@linkplain TestContext#markApplicationContextDirty marked as dirty},
|
||||
* and the
|
||||
* If the current test method of the supplied {@linkplain TestContext test
|
||||
* context} is annotated with {@code @DirtiesContext} and the {@linkplain
|
||||
* DirtiesContext#methodMode() method mode} is set to {@link
|
||||
* MethodMode#BEFORE_METHOD BEFORE_METHOD}, or if the test class is
|
||||
* annotated with {@code @DirtiesContext} and the {@linkplain
|
||||
* DirtiesContext#classMode() class mode} is set to {@link
|
||||
* ClassMode#BEFORE_EACH_TEST_METHOD BEFORE_EACH_TEST_METHOD}, the
|
||||
* {@linkplain ApplicationContext application context} of the test context
|
||||
* will be {@linkplain TestContext#markApplicationContextDirty marked as dirty} and the
|
||||
* {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE
|
||||
* REINJECT_DEPENDENCIES_ATTRIBUTE} in the test context will be set to {@code true}.
|
||||
* @since 4.2
|
||||
*/
|
||||
@Override
|
||||
public void beforeTestMethod(TestContext testContext) throws Exception {
|
||||
beforeOrAfterTestMethod(testContext, "Before", BEFORE_METHOD, BEFORE_EACH_TEST_METHOD);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the current test method of the supplied {@linkplain TestContext test
|
||||
* context} is annotated with {@code @DirtiesContext} and the {@linkplain
|
||||
* DirtiesContext#methodMode() method mode} is set to {@link
|
||||
* MethodMode#AFTER_METHOD AFTER_METHOD}, or if the test class is
|
||||
* annotated with {@code @DirtiesContext} and the {@linkplain
|
||||
* DirtiesContext#classMode() class mode} is set to {@link
|
||||
* ClassMode#AFTER_EACH_TEST_METHOD AFTER_EACH_TEST_METHOD}, the
|
||||
* {@linkplain ApplicationContext application context} of the test context
|
||||
* will be {@linkplain TestContext#markApplicationContextDirty marked as dirty} and the
|
||||
* {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE
|
||||
* REINJECT_DEPENDENCIES_ATTRIBUTE} in the test context will be set to {@code true}.
|
||||
*/
|
||||
@Override
|
||||
public void afterTestMethod(TestContext testContext) throws Exception {
|
||||
beforeOrAfterTestMethod(testContext, "After", AFTER_METHOD, AFTER_EACH_TEST_METHOD);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the test class of the supplied {@linkplain TestContext test context}
|
||||
* is annotated with {@code @DirtiesContext} and the {@linkplain
|
||||
* DirtiesContext#classMode() class mode} is set to {@link
|
||||
* ClassMode#AFTER_CLASS AFTER_CLASS}, the {@linkplain ApplicationContext
|
||||
* application context} of the test context will be
|
||||
* {@linkplain TestContext#markApplicationContextDirty marked as dirty}, and the
|
||||
* {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE
|
||||
* REINJECT_DEPENDENCIES_ATTRIBUTE} in the test context will be set to
|
||||
* {@code true}.
|
||||
*/
|
||||
@Override
|
||||
public void afterTestClass(TestContext testContext) throws Exception {
|
||||
Class<?> testClass = testContext.getTestClass();
|
||||
Assert.notNull(testClass, "The test class of the supplied TestContext must not be null");
|
||||
|
||||
final String annotationType = DirtiesContext.class.getName();
|
||||
AnnotationAttributes annAttrs = AnnotatedElementUtils.getAnnotationAttributes(testClass, annotationType);
|
||||
boolean dirtiesContext = annAttrs != null;
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("After test class: context %s, dirtiesContext [%s].", testContext,
|
||||
dirtiesContext));
|
||||
}
|
||||
if (dirtiesContext) {
|
||||
HierarchyMode hierarchyMode = annAttrs.<HierarchyMode> getEnum("hierarchyMode");
|
||||
dirtyContext(testContext, hierarchyMode);
|
||||
}
|
||||
beforeOrAfterTestClass(testContext, "After", AFTER_CLASS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the {@linkplain ApplicationContext application context} of the supplied
|
||||
* {@linkplain TestContext test context} as
|
||||
* {@linkplain TestContext#markApplicationContextDirty(DirtiesContext.HierarchyMode) dirty}
|
||||
* and sets {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE}
|
||||
* in the test context to {@code true}.
|
||||
* and sets {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE
|
||||
* REINJECT_DEPENDENCIES_ATTRIBUTE} in the test context to {@code true}.
|
||||
* @param testContext the test context whose application context should
|
||||
* marked as dirty
|
||||
* @param hierarchyMode the context cache clearing mode to be applied if the
|
||||
@@ -141,4 +144,62 @@ public class DirtiesContextTestExecutionListener extends AbstractTestExecutionLi
|
||||
testContext.setAttribute(DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE, Boolean.TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the actual work for {@link #beforeTestMethod} and {@link #afterTestMethod}.
|
||||
* @since 4.2
|
||||
*/
|
||||
private void beforeOrAfterTestMethod(TestContext testContext, String phase, MethodMode requiredMethodMode,
|
||||
ClassMode requiredClassMode) 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");
|
||||
|
||||
final String annotationType = DirtiesContext.class.getName();
|
||||
AnnotationAttributes methodAnnAttrs = AnnotatedElementUtils.getAnnotationAttributes(testMethod, annotationType);
|
||||
AnnotationAttributes classAnnAttrs = AnnotatedElementUtils.getAnnotationAttributes(testClass, annotationType);
|
||||
boolean methodAnnotated = methodAnnAttrs != null;
|
||||
boolean classAnnotated = classAnnAttrs != null;
|
||||
MethodMode methodMode = methodAnnotated ? methodAnnAttrs.<MethodMode> getEnum("methodMode") : null;
|
||||
ClassMode classMode = classAnnotated ? classAnnAttrs.<ClassMode> getEnum("classMode") : null;
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format(
|
||||
"%s test method: context %s, class annotated with @DirtiesContext [%s] with mode [%s], method annotated with @DirtiesContext [%s] with mode [%s].",
|
||||
phase, testContext, classAnnotated, classMode, methodAnnotated, methodMode));
|
||||
}
|
||||
|
||||
if ((methodMode == requiredMethodMode) || (classMode == requiredClassMode)) {
|
||||
HierarchyMode hierarchyMode = methodAnnotated ? methodAnnAttrs.<HierarchyMode> getEnum("hierarchyMode")
|
||||
: classAnnAttrs.<HierarchyMode> getEnum("hierarchyMode");
|
||||
dirtyContext(testContext, hierarchyMode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the actual work for {@link #beforeTestClass} and {@link #afterTestClass}.
|
||||
* @since 4.2
|
||||
*/
|
||||
private void beforeOrAfterTestClass(TestContext testContext, String phase, ClassMode requiredClassMode)
|
||||
throws Exception {
|
||||
Class<?> testClass = testContext.getTestClass();
|
||||
Assert.notNull(testClass, "The test class of the supplied TestContext must not be null");
|
||||
|
||||
final String annotationType = DirtiesContext.class.getName();
|
||||
AnnotationAttributes classAnnAttrs = AnnotatedElementUtils.getAnnotationAttributes(testClass, annotationType);
|
||||
boolean classAnnotated = classAnnAttrs != null;
|
||||
ClassMode classMode = classAnnotated ? classAnnAttrs.<ClassMode> getEnum("classMode") : null;
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format(
|
||||
"%s test class: context %s, class annotated with @DirtiesContext [%s] with mode [%s].", phase,
|
||||
testContext, classAnnotated, classMode));
|
||||
}
|
||||
|
||||
if (classMode == requiredClassMode) {
|
||||
HierarchyMode hierarchyMode = classAnnAttrs.<HierarchyMode> getEnum("hierarchyMode");
|
||||
dirtyContext(testContext, hierarchyMode);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user