001/*
002 * Copyright 2002-2015 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.test.context.support;
018
019import java.lang.reflect.Method;
020
021import org.apache.commons.logging.Log;
022import org.apache.commons.logging.LogFactory;
023
024import org.springframework.context.ApplicationContext;
025import org.springframework.core.annotation.AnnotatedElementUtils;
026import org.springframework.test.annotation.DirtiesContext;
027import org.springframework.test.annotation.DirtiesContext.ClassMode;
028import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
029import org.springframework.test.annotation.DirtiesContext.MethodMode;
030import org.springframework.test.context.TestContext;
031import org.springframework.util.Assert;
032
033/**
034 * Abstract base class for {@code TestExecutionListener} implementations that
035 * provide support for marking the {@code ApplicationContext} associated with
036 * a test as <em>dirty</em> for both test classes and test methods annotated
037 * with the {@link DirtiesContext @DirtiesContext} annotation.
038 *
039 * <p>The core functionality for this class was extracted from
040 * {@link DirtiesContextTestExecutionListener} in Spring Framework 4.2.
041 *
042 * @author Sam Brannen
043 * @author Juergen Hoeller
044 * @since 4.2
045 * @see DirtiesContext
046 */
047public abstract class AbstractDirtiesContextTestExecutionListener extends AbstractTestExecutionListener {
048
049        private static final Log logger = LogFactory.getLog(AbstractDirtiesContextTestExecutionListener.class);
050
051
052        @Override
053        public abstract int getOrder();
054
055        /**
056         * Mark the {@linkplain ApplicationContext application context} of the supplied
057         * {@linkplain TestContext test context} as
058         * {@linkplain TestContext#markApplicationContextDirty(DirtiesContext.HierarchyMode) dirty}
059         * and set {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE
060         * REINJECT_DEPENDENCIES_ATTRIBUTE} in the test context to {@code true}.
061         * @param testContext the test context whose application context should
062         * be marked as dirty
063         * @param hierarchyMode the context cache clearing mode to be applied if the
064         * context is part of a hierarchy; may be {@code null}
065         * @since 3.2.2
066         */
067        protected void dirtyContext(TestContext testContext, HierarchyMode hierarchyMode) {
068                testContext.markApplicationContextDirty(hierarchyMode);
069                testContext.setAttribute(DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE, Boolean.TRUE);
070        }
071
072        /**
073         * Perform the actual work for {@link #beforeTestMethod} and {@link #afterTestMethod}
074         * by dirtying the context if appropriate (i.e., according to the required modes).
075         * @param testContext the test context whose application context should
076         * potentially be marked as dirty; never {@code null}
077         * @param requiredMethodMode the method mode required for a context to
078         * be marked dirty in the current phase; never {@code null}
079         * @param requiredClassMode the class mode required for a context to
080         * be marked dirty in the current phase; never {@code null}
081         * @throws Exception allows any exception to propagate
082         * @since 4.2
083         * @see #dirtyContext
084         */
085        protected void beforeOrAfterTestMethod(TestContext testContext, MethodMode requiredMethodMode,
086                        ClassMode requiredClassMode) throws Exception {
087
088                Assert.notNull(testContext, "TestContext must not be null");
089                Assert.notNull(requiredMethodMode, "requiredMethodMode must not be null");
090                Assert.notNull(requiredClassMode, "requiredClassMode must not be null");
091
092                Class<?> testClass = testContext.getTestClass();
093                Method testMethod = testContext.getTestMethod();
094                Assert.notNull(testClass, "The test class of the supplied TestContext must not be null");
095                Assert.notNull(testMethod, "The test method of the supplied TestContext must not be null");
096
097                DirtiesContext methodAnn = AnnotatedElementUtils.findMergedAnnotation(testMethod, DirtiesContext.class);
098                DirtiesContext classAnn = AnnotatedElementUtils.findMergedAnnotation(testClass, DirtiesContext.class);
099                boolean methodAnnotated = (methodAnn != null);
100                boolean classAnnotated = (classAnn != null);
101                MethodMode methodMode = (methodAnnotated ? methodAnn.methodMode() : null);
102                ClassMode classMode = (classAnnotated ? classAnn.classMode() : null);
103
104                if (logger.isDebugEnabled()) {
105                        String phase = (requiredClassMode.name().startsWith("BEFORE") ? "Before" : "After");
106                        logger.debug(String.format("%s test method: context %s, class annotated with @DirtiesContext [%s] "
107                                        + "with mode [%s], method annotated with @DirtiesContext [%s] with mode [%s].", phase, testContext,
108                                classAnnotated, classMode, methodAnnotated, methodMode));
109                }
110
111                if ((methodMode == requiredMethodMode) || (classMode == requiredClassMode)) {
112                        HierarchyMode hierarchyMode = (methodAnnotated ? methodAnn.hierarchyMode() : classAnn.hierarchyMode());
113                        dirtyContext(testContext, hierarchyMode);
114                }
115        }
116
117        /**
118         * Perform the actual work for {@link #beforeTestClass} and {@link #afterTestClass}
119         * by dirtying the context if appropriate (i.e., according to the required mode).
120         * @param testContext the test context whose application context should
121         * potentially be marked as dirty; never {@code null}
122         * @param requiredClassMode the class mode required for a context to
123         * be marked dirty in the current phase; never {@code null}
124         * @throws Exception allows any exception to propagate
125         * @since 4.2
126         * @see #dirtyContext
127         */
128        protected void beforeOrAfterTestClass(TestContext testContext, ClassMode requiredClassMode) throws Exception {
129                Assert.notNull(testContext, "TestContext must not be null");
130                Assert.notNull(requiredClassMode, "requiredClassMode must not be null");
131
132                Class<?> testClass = testContext.getTestClass();
133                Assert.notNull(testClass, "The test class of the supplied TestContext must not be null");
134
135                DirtiesContext dirtiesContext = AnnotatedElementUtils.findMergedAnnotation(testClass, DirtiesContext.class);
136                boolean classAnnotated = (dirtiesContext != null);
137                ClassMode classMode = (classAnnotated ? dirtiesContext.classMode() : null);
138
139                if (logger.isDebugEnabled()) {
140                        String phase = (requiredClassMode.name().startsWith("BEFORE") ? "Before" : "After");
141                        logger.debug(String.format(
142                                "%s test class: context %s, class annotated with @DirtiesContext [%s] with mode [%s].", phase,
143                                testContext, classAnnotated, classMode));
144                }
145
146                if (classMode == requiredClassMode) {
147                        dirtyContext(testContext, dirtiesContext.hierarchyMode());
148                }
149        }
150
151}