diff --git a/spring-test/src/main/java/org/springframework/test/annotation/DirtiesContext.java b/spring-test/src/main/java/org/springframework/test/annotation/DirtiesContext.java index a7f035097a1..4bdb423c5bc 100644 --- a/spring-test/src/main/java/org/springframework/test/annotation/DirtiesContext.java +++ b/spring-test/src/main/java/org/springframework/test/annotation/DirtiesContext.java @@ -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 dirty and should be closed: + * associated with a test is dirty and should therefore be closed + * and removed from the context cache. * - * - * - *

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. + *

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

{@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 dirty 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 dirty 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}. * *

As of Spring Framework 4.0, this annotation may be used as a * meta-annotation to create custom composed annotations. * + *

Supported Test Phases

+ * + * * @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 modes 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 + * dirty before the corresponding test method. + */ + BEFORE_METHOD, + + /** + * The associated {@code ApplicationContext} will be marked as + * dirty after the corresponding test method. + */ + AFTER_METHOD; + } + /** * Defines modes 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 - * dirty after the test class. + * dirty before the test class. + * + * @since 4.2 */ - AFTER_CLASS, + BEFORE_CLASS, + + /** + * The associated {@code ApplicationContext} will be marked as + * dirty before each test method in the class. + * + * @since 4.2 + */ + BEFORE_EACH_TEST_METHOD, /** * The associated {@code ApplicationContext} will be marked as * dirty after each test method in the class. */ - AFTER_EACH_TEST_METHOD; + AFTER_EACH_TEST_METHOD, + + /** + * The associated {@code ApplicationContext} will be marked as + * dirty after the test class. + */ + AFTER_CLASS; } /** @@ -119,13 +169,23 @@ public @interface DirtiesContext { } + /** + * The mode to use when a test method is annotated with + * {@code @DirtiesContext}. + *

Defaults to {@link MethodMode#AFTER_METHOD AFTER_METHOD}. + *

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 mode to use when a test class is annotated with * {@code @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 {@code @DirtiesContext} annotation on a - * test method is sufficient. + *

Setting the class mode on an annotated test method has no meaning. + * For method-level control, use {@link #methodMode} instead. * * @since 3.0 */ diff --git a/spring-test/src/main/java/org/springframework/test/context/support/DirtiesContextTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/support/DirtiesContextTestExecutionListener.java index 28473a24f47..7434a6d81c5 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/DirtiesContextTestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/DirtiesContextTestExecutionListener.java @@ -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 dirty 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. 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. getEnum("hierarchyMode") - : classAnnAttrs. 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. 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. getEnum("methodMode") : null; + ClassMode classMode = classAnnotated ? classAnnAttrs. 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. getEnum("hierarchyMode") + : classAnnAttrs. 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. 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. getEnum("hierarchyMode"); + dirtyContext(testContext, hierarchyMode); + } + } + } diff --git a/spring-test/src/test/java/org/springframework/test/context/support/DirtiesContextTestExecutionListenerTests.java b/spring-test/src/test/java/org/springframework/test/context/support/DirtiesContextTestExecutionListenerTests.java index 45c4d2657b9..23633a9de63 100644 --- a/spring-test/src/test/java/org/springframework/test/context/support/DirtiesContextTestExecutionListenerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/DirtiesContextTestExecutionListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 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. @@ -20,6 +20,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import org.junit.Test; + import org.mockito.BDDMockito; import org.springframework.test.annotation.DirtiesContext; @@ -28,8 +29,10 @@ import org.springframework.test.annotation.DirtiesContext.HierarchyMode; import org.springframework.test.context.TestContext; import static org.mockito.BDDMockito.*; + import static org.springframework.test.annotation.DirtiesContext.ClassMode.*; import static org.springframework.test.annotation.DirtiesContext.HierarchyMode.*; +import static org.springframework.test.annotation.DirtiesContext.MethodMode.*; /** * Unit tests for {@link DirtiesContextTestExecutionListener}. @@ -44,64 +47,113 @@ public class DirtiesContextTestExecutionListenerTests { @Test - public void afterTestMethodForDirtiesContextDeclaredLocallyOnMethod() throws Exception { + public void beforeAndAfterTestMethodForDirtiesContextDeclaredLocallyOnMethodWithBeforeMethodMode() throws Exception { Class clazz = getClass(); BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); - given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("dirtiesContextDeclaredLocally")); + given(testContext.getTestMethod()).willReturn( + clazz.getDeclaredMethod("dirtiesContextDeclaredLocallyWithBeforeMethodMode")); + listener.beforeTestMethod(testContext); + verify(testContext, times(1)).markApplicationContextDirty(EXHAUSTIVE); listener.afterTestMethod(testContext); verify(testContext, times(1)).markApplicationContextDirty(EXHAUSTIVE); } @Test - public void afterTestMethodForDirtiesContextDeclaredOnMethodViaMetaAnnotation() throws Exception { + public void beforeAndAfterTestMethodForDirtiesContextDeclaredLocallyOnMethodWithAfterMethodMode() throws Exception { Class clazz = getClass(); BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); - given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("dirtiesContextDeclaredViaMetaAnnotation")); + given(testContext.getTestMethod()).willReturn( + clazz.getDeclaredMethod("dirtiesContextDeclaredLocallyWithAfterMethodMode")); + listener.beforeTestMethod(testContext); + verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); listener.afterTestMethod(testContext); verify(testContext, times(1)).markApplicationContextDirty(EXHAUSTIVE); } @Test - public void afterTestMethodForDirtiesContextDeclaredLocallyOnClassAfterEachTestMethod() throws Exception { + public void beforeAndAfterTestMethodForDirtiesContextDeclaredOnMethodViaMetaAnnotationWithAfterMethodMode() + throws Exception { + Class clazz = getClass(); + BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); + given(testContext.getTestMethod()).willReturn( + clazz.getDeclaredMethod("dirtiesContextDeclaredViaMetaAnnotationWithAfterMethodMode")); + listener.beforeTestMethod(testContext); + verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); + listener.afterTestMethod(testContext); + verify(testContext, times(1)).markApplicationContextDirty(EXHAUSTIVE); + } + + @Test + public void beforeAndAfterTestMethodForDirtiesContextDeclaredLocallyOnClassBeforeEachTestMethod() throws Exception { + Class clazz = DirtiesContextDeclaredLocallyBeforeEachTestMethod.class; + BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); + given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("clean")); + listener.beforeTestMethod(testContext); + verify(testContext, times(1)).markApplicationContextDirty(EXHAUSTIVE); + listener.afterTestMethod(testContext); + verify(testContext, times(1)).markApplicationContextDirty(EXHAUSTIVE); + } + + @Test + public void beforeAndAfterTestMethodForDirtiesContextDeclaredLocallyOnClassAfterEachTestMethod() throws Exception { Class clazz = DirtiesContextDeclaredLocallyAfterEachTestMethod.class; BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("clean")); + listener.beforeTestMethod(testContext); + verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); listener.afterTestMethod(testContext); verify(testContext, times(1)).markApplicationContextDirty(EXHAUSTIVE); } @Test - public void afterTestMethodForDirtiesContextDeclaredViaMetaAnnotationOnClassAfterEachTestMethod() throws Exception { + public void beforeAndAfterTestMethodForDirtiesContextDeclaredViaMetaAnnotationOnClassAfterEachTestMethod() + throws Exception { Class clazz = DirtiesContextDeclaredViaMetaAnnotationAfterEachTestMethod.class; BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("clean")); + listener.beforeTestMethod(testContext); + verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); listener.afterTestMethod(testContext); verify(testContext, times(1)).markApplicationContextDirty(EXHAUSTIVE); } @Test - public void afterTestMethodForDirtiesContextDeclaredLocallyOnClassAfterClass() throws Exception { + public void beforeAndAfterTestMethodForDirtiesContextDeclaredLocallyOnClassBeforeClass() throws Exception { + Class clazz = DirtiesContextDeclaredLocallyBeforeClass.class; + BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); + given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("clean")); + listener.beforeTestMethod(testContext); + listener.afterTestMethod(testContext); + verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); + } + + @Test + public void beforeAndAfterTestMethodForDirtiesContextDeclaredLocallyOnClassAfterClass() throws Exception { Class clazz = DirtiesContextDeclaredLocallyAfterClass.class; BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("clean")); + listener.beforeTestMethod(testContext); listener.afterTestMethod(testContext); - verify(testContext, times(0)).markApplicationContextDirty(EXHAUSTIVE); + verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); } @Test - public void afterTestMethodForDirtiesContextDeclaredViaMetaAnnotationOnClassAfterClass() throws Exception { + public void beforeAndAfterTestMethodForDirtiesContextDeclaredViaMetaAnnotationOnClassAfterClass() throws Exception { Class clazz = DirtiesContextDeclaredViaMetaAnnotationAfterClass.class; BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("clean")); + listener.beforeTestMethod(testContext); listener.afterTestMethod(testContext); - verify(testContext, times(0)).markApplicationContextDirty(EXHAUSTIVE); + verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); } @Test - public void afterTestMethodForDirtiesContextViaMetaAnnotationWithOverrides() throws Exception { + public void beforeAndAfterTestMethodForDirtiesContextViaMetaAnnotationWithOverrides() throws Exception { Class clazz = DirtiesContextViaMetaAnnotationWithOverrides.class; BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("clean")); + listener.beforeTestMethod(testContext); + verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); listener.afterTestMethod(testContext); verify(testContext, times(1)).markApplicationContextDirty(CURRENT_LEVEL); } @@ -109,77 +161,113 @@ public class DirtiesContextTestExecutionListenerTests { // ------------------------------------------------------------------------- @Test - public void afterTestClassForDirtiesContextDeclaredLocallyOnMethod() throws Exception { + public void beforeAndAfterTestClassForDirtiesContextDeclaredLocallyOnMethod() throws Exception { Class clazz = getClass(); BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); + listener.beforeTestClass(testContext); listener.afterTestClass(testContext); - verify(testContext, times(0)).markApplicationContextDirty(EXHAUSTIVE); + verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); } @Test - public void afterTestClassForDirtiesContextDeclaredLocallyOnClassAfterEachTestMethod() throws Exception { + public void beforeAndAfterTestClassForDirtiesContextDeclaredLocallyOnClassBeforeEachTestMethod() throws Exception { + Class clazz = DirtiesContextDeclaredLocallyBeforeEachTestMethod.class; + BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); + listener.beforeTestClass(testContext); + listener.afterTestClass(testContext); + verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); + } + + @Test + public void beforeAndAfterTestClassForDirtiesContextDeclaredLocallyOnClassAfterEachTestMethod() throws Exception { Class clazz = DirtiesContextDeclaredLocallyAfterEachTestMethod.class; BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); + listener.beforeTestClass(testContext); listener.afterTestClass(testContext); - verify(testContext, times(1)).markApplicationContextDirty(EXHAUSTIVE); + verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); } @Test - public void afterTestClassForDirtiesContextDeclaredViaMetaAnnotationOnClassAfterEachTestMethod() throws Exception { + public void beforeAndAfterTestClassForDirtiesContextDeclaredViaMetaAnnotationOnClassAfterEachTestMethod() + throws Exception { Class clazz = DirtiesContextDeclaredViaMetaAnnotationAfterEachTestMethod.class; BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); + listener.beforeTestClass(testContext); + listener.afterTestClass(testContext); + verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); + } + + @Test + public void beforeAndAfterTestClassForDirtiesContextDeclaredLocallyOnClassBeforeClass() throws Exception { + Class clazz = DirtiesContextDeclaredLocallyBeforeClass.class; + BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); + listener.beforeTestClass(testContext); + verify(testContext, times(1)).markApplicationContextDirty(EXHAUSTIVE); listener.afterTestClass(testContext); verify(testContext, times(1)).markApplicationContextDirty(EXHAUSTIVE); } @Test - public void afterTestClassForDirtiesContextDeclaredLocallyOnClassAfterClass() throws Exception { + public void beforeAndAfterTestClassForDirtiesContextDeclaredLocallyOnClassAfterClass() throws Exception { Class clazz = DirtiesContextDeclaredLocallyAfterClass.class; BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); + listener.beforeTestClass(testContext); + verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); listener.afterTestClass(testContext); verify(testContext, times(1)).markApplicationContextDirty(EXHAUSTIVE); } @Test - public void afterTestClassForDirtiesContextDeclaredViaMetaAnnotationOnClassAfterClass() throws Exception { + public void beforeAndAfterTestClassForDirtiesContextDeclaredViaMetaAnnotationOnClassAfterClass() throws Exception { Class clazz = DirtiesContextDeclaredViaMetaAnnotationAfterClass.class; BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); + listener.beforeTestClass(testContext); + verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); listener.afterTestClass(testContext); verify(testContext, times(1)).markApplicationContextDirty(EXHAUSTIVE); } @Test - public void afterTestClassForDirtiesContextDeclaredViaMetaAnnotationWithOverrides() throws Exception { + public void beforeAndAfterTestClassForDirtiesContextDeclaredViaMetaAnnotationWithOverrides() throws Exception { Class clazz = DirtiesContextViaMetaAnnotationWithOverrides.class; BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); + listener.beforeTestClass(testContext); listener.afterTestClass(testContext); - verify(testContext, times(1)).markApplicationContextDirty(CURRENT_LEVEL); + verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); } @Test - public void afterTestClassForDirtiesContextDeclaredViaMetaAnnotationWithOverridenAttributes() throws Exception { + public void beforeAndAfterTestClassForDirtiesContextDeclaredViaMetaAnnotationWithOverridenAttributes() + throws Exception { Class clazz = DirtiesContextViaMetaAnnotationWithOverridenAttributes.class; BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); + listener.beforeTestClass(testContext); + verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); listener.afterTestClass(testContext); verify(testContext, times(1)).markApplicationContextDirty(EXHAUSTIVE); } // ------------------------------------------------------------------------- - @DirtiesContext - void dirtiesContextDeclaredLocally() { + @DirtiesContext(methodMode = BEFORE_METHOD) + void dirtiesContextDeclaredLocallyWithBeforeMethodMode() { /* no-op */ } - @MetaDirty - void dirtiesContextDeclaredViaMetaAnnotation() { + @DirtiesContext + void dirtiesContextDeclaredLocallyWithAfterMethodMode() { + /* no-op */ + } + + @MetaDirtyAfterMethod + void dirtiesContextDeclaredViaMetaAnnotationWithAfterMethodMode() { /* no-op */ } @DirtiesContext @Retention(RetentionPolicy.RUNTIME) - static @interface MetaDirty { + static @interface MetaDirtyAfterMethod { } @DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) @@ -192,6 +280,14 @@ public class DirtiesContextTestExecutionListenerTests { static @interface MetaDirtyAfterClass { } + @DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) + static class DirtiesContextDeclaredLocallyBeforeEachTestMethod { + + void clean() { + /* no-op */ + } + } + @DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) static class DirtiesContextDeclaredLocallyAfterEachTestMethod { @@ -217,6 +313,14 @@ public class DirtiesContextTestExecutionListenerTests { } } + @DirtiesContext(classMode = BEFORE_CLASS) + static class DirtiesContextDeclaredLocallyBeforeClass { + + void clean() { + /* no-op */ + } + } + @DirtiesContext(classMode = AFTER_CLASS) static class DirtiesContextDeclaredLocallyAfterClass { diff --git a/src/asciidoc/testing.adoc b/src/asciidoc/testing.adoc index 6763d673da1..10e3bf09ca9 100644 --- a/src/asciidoc/testing.adoc +++ b/src/asciidoc/testing.adoc @@ -598,19 +598,17 @@ The following example demonstrates how to declare _inlined_ properties. Indicates that the underlying Spring `ApplicationContext` has been __dirtied__ during the execution of a test (i.e., modified or corrupted in some manner -- for example, by -changing the state of a singleton bean) and should be closed, regardless of whether the -test passed. When an application context is marked __dirty__, it is removed from the -testing framework's cache and closed. As a consequence, the underlying Spring container -will be rebuilt for any subsequent test that requires a context with the same -configuration metadata. +changing the state of a singleton bean) and should be closed. When an application +context is marked __dirty__, it is removed from the testing framework's cache and +closed. As a consequence, the underlying Spring container will be rebuilt for any +subsequent test that requires a context with the same configuration metadata. + `@DirtiesContext` can be used as both a class-level and method-level annotation within -the same test class. In such scenarios, the `ApplicationContext` is marked as __dirty__ -after any such annotated method as well as after the entire class. If the `ClassMode` is -set to `AFTER_EACH_TEST_METHOD`, the context is marked dirty after each test method in -the class. +the same class or class hierarchy. In such scenarios, the `ApplicationContext` is marked +as __dirty__ before or after any such annotated method as well as before or after the +current test class, depending on the configured `methodMode` and `classMode`. + @@ -619,6 +617,22 @@ configuration scenarios: + +** Before the current test class, when declared on a class with class mode set to +`BEFORE_CLASS`. + ++ + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + **@DirtiesContext(classMode = BEFORE_CLASS)** + public class FreshContextTests { + // some tests that require a new Spring container + } +---- + ++ + ** After the current test class, when declared on a class with class mode set to `AFTER_CLASS` (i.e., the default class mode). @@ -635,6 +649,22 @@ configuration scenarios: + +** Before each test method in the current test class, when declared on a class with class +mode set to `BEFORE_EACH_TEST_METHOD.` + ++ + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + **@DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD)** + public class FreshContextTests { + // some tests that require a new Spring container + } +---- + ++ + ** After each test method in the current test class, when declared on a class with class mode set to `AFTER_EACH_TEST_METHOD.` @@ -643,7 +673,7 @@ mode set to `AFTER_EACH_TEST_METHOD.` [source,java,indent=0] [subs="verbatim,quotes"] ---- - **@DirtiesContext**(**classMode** = ClassMode.AFTER_EACH_TEST_METHOD) + **@DirtiesContext(classMode = AFTER_EACH_TEST_METHOD)** public class ContextDirtyingTests { // some tests that result in the Spring container being dirtied } @@ -651,7 +681,25 @@ mode set to `AFTER_EACH_TEST_METHOD.` + -** After the current test, when declared on a method. +** Before the current test, when declared on a method with the method mode set to +`BEFORE_METHOD`. + ++ + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + **@DirtiesContext(methodMode = BEFORE_METHOD)** + @Test + public void testProcessWhichRequiresFreshAppCtx() { + // some logic that requires a new Spring container + } +---- + ++ + +** After the current test, when declared on a method with the method mode set to +`AFTER_METHOD` (i.e., the default method mode). + @@ -693,7 +741,7 @@ specified instead, as seen below. public class ExtendedTests extends BaseTests { @Test - @DirtiesContext(**hierarchyMode = HierarchyMode.CURRENT_LEVEL**) + @DirtiesContext(**hierarchyMode = CURRENT_LEVEL**) public void test() { // some logic that results in the child context being dirtied }