001/*
002 * Copyright 2002-2016 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;
018
019import java.lang.reflect.Method;
020import java.util.ArrayList;
021import java.util.Collections;
022import java.util.List;
023
024import org.apache.commons.logging.Log;
025import org.apache.commons.logging.LogFactory;
026
027import org.springframework.util.Assert;
028import org.springframework.util.ReflectionUtils;
029
030/**
031 * {@code TestContextManager} is the main entry point into the <em>Spring
032 * TestContext Framework</em>.
033 *
034 * <p>Specifically, a {@code TestContextManager} is responsible for managing a
035 * single {@link TestContext} and signaling events to all registered
036 * {@link TestExecutionListener TestExecutionListeners} at the following test
037 * execution points:
038 *
039 * <ul>
040 * <li>{@link #beforeTestClass() before test class execution}: prior to any
041 * <em>before class callbacks</em> of a particular testing framework (e.g.,
042 * JUnit 4's {@link org.junit.BeforeClass @BeforeClass})</li>
043 * <li>{@link #prepareTestInstance(Object) test instance preparation}:
044 * immediately following instantiation of the test instance</li>
045 * <li>{@link #beforeTestMethod(Object, Method) before test method execution}:
046 * prior to any <em>before method callbacks</em> of a particular testing framework
047 * (e.g., JUnit 4's {@link org.junit.Before @Before})</li>
048 * <li>{@link #afterTestMethod(Object, Method, Throwable) after test method
049 * execution}: after any <em>after method callbacks</em> of a particular testing
050 * framework (e.g., JUnit 4's {@link org.junit.After @After})</li>
051 * <li>{@link #afterTestClass() after test class execution}: after any
052 * <em>after class callbacks</em> of a particular testing framework (e.g., JUnit
053 * 4's {@link org.junit.AfterClass @AfterClass})</li>
054 * </ul>
055 *
056 * <p>Support for loading and accessing
057 * {@link org.springframework.context.ApplicationContext application contexts},
058 * dependency injection of test instances,
059 * {@link org.springframework.transaction.annotation.Transactional transactional}
060 * execution of test methods, etc. is provided by
061 * {@link SmartContextLoader ContextLoaders} and {@link TestExecutionListener
062 * TestExecutionListeners}, which are configured via
063 * {@link ContextConfiguration @ContextConfiguration} and
064 * {@link TestExecutionListeners @TestExecutionListeners}.
065 *
066 * <p>Bootstrapping of the {@code TestContext}, the default {@code ContextLoader},
067 * default {@code TestExecutionListeners}, and their collaborators is performed
068 * by a {@link TestContextBootstrapper}, which is configured via
069 * {@link BootstrapWith @BootstrapWith}.
070 *
071 * @author Sam Brannen
072 * @author Juergen Hoeller
073 * @since 2.5
074 * @see BootstrapWith
075 * @see BootstrapContext
076 * @see TestContextBootstrapper
077 * @see TestContext
078 * @see TestExecutionListener
079 * @see TestExecutionListeners
080 * @see ContextConfiguration
081 * @see ContextHierarchy
082 */
083public class TestContextManager {
084
085        private static final Log logger = LogFactory.getLog(TestContextManager.class);
086
087        private final TestContext testContext;
088
089        private final List<TestExecutionListener> testExecutionListeners = new ArrayList<TestExecutionListener>();
090
091
092        /**
093         * Construct a new {@code TestContextManager} for the supplied {@linkplain Class test class}.
094         * <p>Delegates to {@link #TestContextManager(TestContextBootstrapper)} with
095         * the {@link TestContextBootstrapper} configured for the test class. If the
096         * {@link BootstrapWith @BootstrapWith} annotation is present on the test
097         * class, either directly or as a meta-annotation, then its
098         * {@link BootstrapWith#value value} will be used as the bootstrapper type;
099         * otherwise, the {@link org.springframework.test.context.support.DefaultTestContextBootstrapper
100         * DefaultTestContextBootstrapper} will be used.
101         * @param testClass the test class to be managed
102         * @see #TestContextManager(TestContextBootstrapper)
103         */
104        public TestContextManager(Class<?> testClass) {
105                this(BootstrapUtils.resolveTestContextBootstrapper(BootstrapUtils.createBootstrapContext(testClass)));
106        }
107
108        /**
109         * Construct a new {@code TestContextManager} using the supplied {@link TestContextBootstrapper}
110         * and {@linkplain #registerTestExecutionListeners register} the necessary
111         * {@link TestExecutionListener TestExecutionListeners}.
112         * <p>Delegates to the supplied {@code TestContextBootstrapper} for building
113         * the {@code TestContext} and retrieving the {@code TestExecutionListeners}.
114         * @param testContextBootstrapper the bootstrapper to use
115         * @see TestContextBootstrapper#buildTestContext
116         * @see TestContextBootstrapper#getTestExecutionListeners
117         * @see #registerTestExecutionListeners
118         */
119        public TestContextManager(TestContextBootstrapper testContextBootstrapper) {
120                this.testContext = testContextBootstrapper.buildTestContext();
121                registerTestExecutionListeners(testContextBootstrapper.getTestExecutionListeners());
122        }
123
124        /**
125         * Get the {@link TestContext} managed by this {@code TestContextManager}.
126         */
127        public final TestContext getTestContext() {
128                return this.testContext;
129        }
130
131        /**
132         * Register the supplied list of {@link TestExecutionListener TestExecutionListeners}
133         * by appending them to the list of listeners used by this {@code TestContextManager}.
134         * @see #registerTestExecutionListeners(TestExecutionListener...)
135         */
136        public void registerTestExecutionListeners(List<TestExecutionListener> testExecutionListeners) {
137                registerTestExecutionListeners(testExecutionListeners.toArray(new TestExecutionListener[testExecutionListeners.size()]));
138        }
139
140        /**
141         * Register the supplied array of {@link TestExecutionListener TestExecutionListeners}
142         * by appending them to the list of listeners used by this {@code TestContextManager}.
143         */
144        public void registerTestExecutionListeners(TestExecutionListener... testExecutionListeners) {
145                for (TestExecutionListener listener : testExecutionListeners) {
146                        if (logger.isTraceEnabled()) {
147                                logger.trace("Registering TestExecutionListener: " + listener);
148                        }
149                        this.testExecutionListeners.add(listener);
150                }
151        }
152
153        /**
154         * Get the current {@link TestExecutionListener TestExecutionListeners}
155         * registered for this {@code TestContextManager}.
156         * <p>Allows for modifications, e.g. adding a listener to the beginning of the list.
157         * However, make sure to keep the list stable while actually executing tests.
158         */
159        public final List<TestExecutionListener> getTestExecutionListeners() {
160                return this.testExecutionListeners;
161        }
162
163        /**
164         * Get a copy of the {@link TestExecutionListener TestExecutionListeners}
165         * registered for this {@code TestContextManager} in reverse order.
166         */
167        private List<TestExecutionListener> getReversedTestExecutionListeners() {
168                List<TestExecutionListener> listenersReversed = new ArrayList<TestExecutionListener>(getTestExecutionListeners());
169                Collections.reverse(listenersReversed);
170                return listenersReversed;
171        }
172
173        /**
174         * Hook for pre-processing a test class <em>before</em> execution of any
175         * tests within the class. Should be called prior to any framework-specific
176         * <em>before class methods</em> (e.g., methods annotated with JUnit's
177         * {@link org.junit.BeforeClass @BeforeClass}).
178         * <p>An attempt will be made to give each registered
179         * {@link TestExecutionListener} a chance to pre-process the test class
180         * execution. If a listener throws an exception, however, the remaining
181         * registered listeners will <strong>not</strong> be called.
182         * @throws Exception if a registered TestExecutionListener throws an
183         * exception
184         * @see #getTestExecutionListeners()
185         */
186        public void beforeTestClass() throws Exception {
187                Class<?> testClass = getTestContext().getTestClass();
188                if (logger.isTraceEnabled()) {
189                        logger.trace("beforeTestClass(): class [" + testClass.getName() + "]");
190                }
191                getTestContext().updateState(null, null, null);
192
193                for (TestExecutionListener testExecutionListener : getTestExecutionListeners()) {
194                        try {
195                                testExecutionListener.beforeTestClass(getTestContext());
196                        }
197                        catch (Throwable ex) {
198                                if (logger.isWarnEnabled()) {
199                                        logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener +
200                                                        "] to process 'before class' callback for test class [" + testClass + "]", ex);
201                                }
202                                ReflectionUtils.rethrowException(ex);
203                        }
204                }
205        }
206
207        /**
208         * Hook for preparing a test instance prior to execution of any individual
209         * test methods, for example for injecting dependencies, etc. Should be
210         * called immediately after instantiation of the test instance.
211         * <p>The managed {@link TestContext} will be updated with the supplied
212         * {@code testInstance}.
213         * <p>An attempt will be made to give each registered
214         * {@link TestExecutionListener} a chance to prepare the test instance. If a
215         * listener throws an exception, however, the remaining registered listeners
216         * will <strong>not</strong> be called.
217         * @param testInstance the test instance to prepare (never {@code null})
218         * @throws Exception if a registered TestExecutionListener throws an exception
219         * @see #getTestExecutionListeners()
220         */
221        public void prepareTestInstance(Object testInstance) throws Exception {
222                Assert.notNull(testInstance, "Test instance must not be null");
223                if (logger.isTraceEnabled()) {
224                        logger.trace("prepareTestInstance(): instance [" + testInstance + "]");
225                }
226                getTestContext().updateState(testInstance, null, null);
227
228                for (TestExecutionListener testExecutionListener : getTestExecutionListeners()) {
229                        try {
230                                testExecutionListener.prepareTestInstance(getTestContext());
231                        }
232                        catch (Throwable ex) {
233                                if (logger.isErrorEnabled()) {
234                                        logger.error("Caught exception while allowing TestExecutionListener [" + testExecutionListener +
235                                                        "] to prepare test instance [" + testInstance + "]", ex);
236                                }
237                                ReflectionUtils.rethrowException(ex);
238                        }
239                }
240        }
241
242        /**
243         * Hook for pre-processing a test <em>before</em> execution of the supplied
244         * {@link Method test method}, for example for setting up test fixtures,
245         * starting a transaction, etc. Should be called prior to any
246         * framework-specific <em>before methods</em> (e.g., methods annotated with
247         * JUnit's {@link org.junit.Before @Before}).
248         * <p>The managed {@link TestContext} will be updated with the supplied
249         * {@code testInstance} and {@code testMethod}.
250         * <p>An attempt will be made to give each registered
251         * {@link TestExecutionListener} a chance to pre-process the test method
252         * execution. If a listener throws an exception, however, the remaining
253         * registered listeners will <strong>not</strong> be called.
254         * @param testInstance the current test instance (never {@code null})
255         * @param testMethod the test method which is about to be executed on the
256         * test instance
257         * @throws Exception if a registered TestExecutionListener throws an exception
258         * @see #getTestExecutionListeners()
259         */
260        public void beforeTestMethod(Object testInstance, Method testMethod) throws Exception {
261                Assert.notNull(testInstance, "Test instance must not be null");
262                if (logger.isTraceEnabled()) {
263                        logger.trace("beforeTestMethod(): instance [" + testInstance + "], method [" + testMethod + "]");
264                }
265                getTestContext().updateState(testInstance, testMethod, null);
266
267                for (TestExecutionListener testExecutionListener : getTestExecutionListeners()) {
268                        try {
269                                testExecutionListener.beforeTestMethod(getTestContext());
270                        }
271                        catch (Throwable ex) {
272                                if (logger.isWarnEnabled()) {
273                                        logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener +
274                                                        "] to process 'before' execution of test method [" + testMethod + "] for test instance [" +
275                                                        testInstance + "]", ex);
276                                }
277                                ReflectionUtils.rethrowException(ex);
278                        }
279                }
280        }
281
282        /**
283         * Hook for post-processing a test <em>after</em> execution of the supplied
284         * {@link Method test method}, for example for tearing down test fixtures,
285         * ending a transaction, etc. Should be called after any framework-specific
286         * <em>after methods</em> (e.g., methods annotated with JUnit's
287         * {@link org.junit.After @After}).
288         * <p>The managed {@link TestContext} will be updated with the supplied
289         * {@code testInstance}, {@code testMethod}, and
290         * {@code exception}.
291         * <p>Each registered {@link TestExecutionListener} will be given a chance to
292         * post-process the test method execution. If a listener throws an
293         * exception, the remaining registered listeners will still be called, but
294         * the first exception thrown will be tracked and rethrown after all
295         * listeners have executed. Note that registered listeners will be executed
296         * in the opposite order in which they were registered.
297         * @param testInstance the current test instance (never {@code null})
298         * @param testMethod the test method which has just been executed on the
299         * test instance
300         * @param exception the exception that was thrown during execution of the
301         * test method or by a TestExecutionListener, or {@code null} if none
302         * was thrown
303         * @throws Exception if a registered TestExecutionListener throws an exception
304         * @see #getTestExecutionListeners()
305         */
306        public void afterTestMethod(Object testInstance, Method testMethod, Throwable exception) throws Exception {
307                Assert.notNull(testInstance, "Test instance must not be null");
308                if (logger.isTraceEnabled()) {
309                        logger.trace("afterTestMethod(): instance [" + testInstance + "], method [" + testMethod +
310                                        "], exception [" + exception + "]");
311                }
312                getTestContext().updateState(testInstance, testMethod, exception);
313
314                Throwable afterTestMethodException = null;
315                // Traverse the TestExecutionListeners in reverse order to ensure proper
316                // "wrapper"-style execution of listeners.
317                for (TestExecutionListener testExecutionListener : getReversedTestExecutionListeners()) {
318                        try {
319                                testExecutionListener.afterTestMethod(getTestContext());
320                        }
321                        catch (Throwable ex) {
322                                if (logger.isWarnEnabled()) {
323                                        logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener +
324                                                        "] to process 'after' execution for test: method [" + testMethod + "], instance [" +
325                                                        testInstance + "], exception [" + exception + "]", ex);
326                                }
327                                if (afterTestMethodException == null) {
328                                        afterTestMethodException = ex;
329                                }
330                        }
331                }
332                if (afterTestMethodException != null) {
333                        ReflectionUtils.rethrowException(afterTestMethodException);
334                }
335        }
336
337        /**
338         * Hook for post-processing a test class <em>after</em> execution of all
339         * tests within the class. Should be called after any framework-specific
340         * <em>after class methods</em> (e.g., methods annotated with JUnit's
341         * {@link org.junit.AfterClass @AfterClass}).
342         * <p>Each registered {@link TestExecutionListener} will be given a chance to
343         * post-process the test class. If a listener throws an exception, the
344         * remaining registered listeners will still be called, but the first
345         * exception thrown will be tracked and rethrown after all listeners have
346         * executed. Note that registered listeners will be executed in the opposite
347         * order in which they were registered.
348         * @throws Exception if a registered TestExecutionListener throws an exception
349         * @see #getTestExecutionListeners()
350         */
351        public void afterTestClass() throws Exception {
352                Class<?> testClass = getTestContext().getTestClass();
353                if (logger.isTraceEnabled()) {
354                        logger.trace("afterTestClass(): class [" + testClass.getName() + "]");
355                }
356                getTestContext().updateState(null, null, null);
357
358                Throwable afterTestClassException = null;
359                // Traverse the TestExecutionListeners in reverse order to ensure proper
360                // "wrapper"-style execution of listeners.
361                for (TestExecutionListener testExecutionListener : getReversedTestExecutionListeners()) {
362                        try {
363                                testExecutionListener.afterTestClass(getTestContext());
364                        }
365                        catch (Throwable ex) {
366                                if (logger.isWarnEnabled()) {
367                                        logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener +
368                                                        "] to process 'after class' callback for test class [" + testClass + "]", ex);
369                                }
370                                if (afterTestClassException == null) {
371                                        afterTestClassException = ex;
372                                }
373                        }
374                }
375                if (afterTestClassException != null) {
376                        ReflectionUtils.rethrowException(afterTestClassException);
377                }
378        }
379
380}