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}