All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.test.context.TestContextManager Maven / Gradle / Ivy

There is a newer version: 6.1.13
Show newest version
/*
 * Copyright 2002-2010 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.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.test.context;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;

/**
 * 

* TestContextManager is the main entry point into the * Spring TestContext Framework, which provides support for loading and * accessing {@link ApplicationContext application contexts}, dependency * injection of test instances, * {@link org.springframework.transaction.annotation.Transactional * transactional} execution of test methods, etc. *

*

* Specifically, a TestContextManager is responsible for managing a * single {@link TestContext} and signaling events to all registered * {@link TestExecutionListener TestExecutionListeners} at well defined test * execution points: *

*
    *
  • {@link #beforeTestClass() before test class execution}: prior to any * before class methods of a particular testing framework (e.g., JUnit * 4's {@link org.junit.BeforeClass @BeforeClass})
  • *
  • {@link #prepareTestInstance(Object) test instance preparation}: * immediately following instantiation of the test instance
  • *
  • {@link #beforeTestMethod(Object,Method) before test method execution}: * prior to any before methods of a particular testing framework (e.g., * JUnit 4's {@link org.junit.Before @Before})
  • *
  • {@link #afterTestMethod(Object,Method,Throwable) after test method * execution}: after any after methods of a particular testing * framework (e.g., JUnit 4's {@link org.junit.After @After})
  • *
  • {@link #afterTestClass() after test class execution}: after any * after class methods of a particular testing framework (e.g., JUnit * 4's {@link org.junit.AfterClass @AfterClass})
  • *
* * @author Sam Brannen * @author Juergen Hoeller * @since 2.5 * @see TestContext * @see TestExecutionListeners * @see ContextConfiguration * @see org.springframework.test.context.transaction.TransactionConfiguration */ public class TestContextManager { private static final String[] DEFAULT_TEST_EXECUTION_LISTENER_CLASS_NAMES = new String[] { "org.springframework.test.context.support.DependencyInjectionTestExecutionListener", "org.springframework.test.context.support.DirtiesContextTestExecutionListener", "org.springframework.test.context.transaction.TransactionalTestExecutionListener" }; private static final Log logger = LogFactory.getLog(TestContextManager.class); /** * Cache of Spring application contexts. This needs to be static, as tests * may be destroyed and recreated between running individual test methods, * for example with JUnit. */ static final ContextCache contextCache = new ContextCache(); private final TestContext testContext; private final List testExecutionListeners = new ArrayList(); /** * Delegates to {@link #TestContextManager(Class, String)} with a value of * null for the default ContextLoader class name. */ public TestContextManager(Class testClass) { this(testClass, null); } /** * Constructs a new TestContextManager for the specified {@link Class test class} * and automatically {@link #registerTestExecutionListeners registers} the * {@link TestExecutionListener TestExecutionListeners} configured for the test class * via the {@link TestExecutionListeners @TestExecutionListeners} annotation. * @param testClass the test class to be managed * @param defaultContextLoaderClassName the name of the default * ContextLoader class to use (may be null) * @see #registerTestExecutionListeners(TestExecutionListener...) * @see #retrieveTestExecutionListeners(Class) */ public TestContextManager(Class testClass, String defaultContextLoaderClassName) { this.testContext = new TestContext(testClass, contextCache, defaultContextLoaderClassName); registerTestExecutionListeners(retrieveTestExecutionListeners(testClass)); } /** * Returns the {@link TestContext} managed by this * TestContextManager. */ protected final TestContext getTestContext() { return this.testContext; } /** * Register the supplied {@link TestExecutionListener TestExecutionListeners} * by appending them to the set of listeners used by this TestContextManager. */ public void registerTestExecutionListeners(TestExecutionListener... testExecutionListeners) { for (TestExecutionListener listener : testExecutionListeners) { if (logger.isTraceEnabled()) { logger.trace("Registering TestExecutionListener: " + listener); } this.testExecutionListeners.add(listener); } } /** * Get the current {@link TestExecutionListener TestExecutionListeners} * registered for this TestContextManager. *

Allows for modifications, e.g. adding a listener to the beginning of the list. * However, make sure to keep the list stable while actually executing tests. */ public final List getTestExecutionListeners() { return this.testExecutionListeners; } /** * Get a copy of the {@link TestExecutionListener TestExecutionListeners} * registered for this TestContextManager in reverse order. */ private List getReversedTestExecutionListeners() { List listenersReversed = new ArrayList(getTestExecutionListeners()); Collections.reverse(listenersReversed); return listenersReversed; } /** * Retrieve an array of newly instantiated {@link TestExecutionListener TestExecutionListeners} * for the specified {@link Class class}. If {@link TestExecutionListeners @TestExecutionListeners} * is not present on the supplied class, the default listeners will be returned. *

Note that the {@link TestExecutionListeners#inheritListeners() inheritListeners} flag of * {@link TestExecutionListeners @TestExecutionListeners} will be taken into consideration. * Specifically, if the inheritListeners flag is set to true, listeners * defined in the annotated class will be appended to the listeners defined in superclasses. * @param clazz the test class for which the listeners should be retrieved * @return an array of TestExecutionListeners for the specified class */ private TestExecutionListener[] retrieveTestExecutionListeners(Class clazz) { Assert.notNull(clazz, "Class must not be null"); Class annotationType = TestExecutionListeners.class; List> classesList = new ArrayList>(); Class declaringClass = AnnotationUtils.findAnnotationDeclaringClass(annotationType, clazz); boolean defaultListeners = false; // Use defaults? if (declaringClass == null) { if (logger.isInfoEnabled()) { logger.info("@TestExecutionListeners is not present for class [" + clazz + "]: using defaults."); } classesList.addAll(getDefaultTestExecutionListenerClasses()); defaultListeners = true; } else { // Traverse the class hierarchy... while (declaringClass != null) { TestExecutionListeners testExecutionListeners = declaringClass.getAnnotation(annotationType); if (logger.isTraceEnabled()) { logger.trace("Retrieved @TestExecutionListeners [" + testExecutionListeners + "] for declaring class [" + declaringClass + "]."); } Class[] valueListenerClasses = testExecutionListeners.value(); Class[] listenerClasses = testExecutionListeners.listeners(); if (!ObjectUtils.isEmpty(valueListenerClasses) && !ObjectUtils.isEmpty(listenerClasses)) { String msg = String.format( "Test class [%s] has been configured with @TestExecutionListeners' 'value' [%s] " + "and 'listeners' [%s] attributes. Use one or the other, but not both.", declaringClass, ObjectUtils.nullSafeToString(valueListenerClasses), ObjectUtils.nullSafeToString(listenerClasses)); logger.error(msg); throw new IllegalStateException(msg); } else if (!ObjectUtils.isEmpty(valueListenerClasses)) { listenerClasses = valueListenerClasses; } if (listenerClasses != null) { classesList.addAll(0, Arrays.> asList(listenerClasses)); } declaringClass = (testExecutionListeners.inheritListeners() ? AnnotationUtils.findAnnotationDeclaringClass(annotationType, declaringClass.getSuperclass()) : null); } } List listeners = new ArrayList(classesList.size()); for (Class listenerClass : classesList) { try { listeners.add(BeanUtils.instantiateClass(listenerClass)); } catch (NoClassDefFoundError err) { if (defaultListeners) { if (logger.isDebugEnabled()) { logger.debug("Could not instantiate default TestExecutionListener class [" + listenerClass.getName() + "]. Specify custom listener classes or make the default listener classes available."); } } else { throw err; } } } return listeners.toArray(new TestExecutionListener[listeners.size()]); } /** * Determine the default {@link TestExecutionListener} classes. */ @SuppressWarnings("unchecked") protected Set> getDefaultTestExecutionListenerClasses() { Set> defaultListenerClasses = new LinkedHashSet>(); for (String className : DEFAULT_TEST_EXECUTION_LISTENER_CLASS_NAMES) { try { defaultListenerClasses.add( (Class) getClass().getClassLoader().loadClass(className)); } catch (Throwable ex) { if (logger.isDebugEnabled()) { logger.debug("Could not load default TestExecutionListener class [" + className + "]. Specify custom listener classes or make the default listener classes available."); } } } return defaultListenerClasses; } /** * Hook for pre-processing a test class before execution of any * tests within the class. Should be called prior to any framework-specific * before class methods (e.g., methods annotated with JUnit's * {@link org.junit.BeforeClass @BeforeClass}). *

An attempt will be made to give each registered * {@link TestExecutionListener} a chance to pre-process the test class * execution. If a listener throws an exception, however, the remaining * registered listeners will not be called. * @throws Exception if a registered TestExecutionListener throws an * exception * @see #getTestExecutionListeners() */ public void beforeTestClass() throws Exception { final Class testClass = getTestContext().getTestClass(); if (logger.isTraceEnabled()) { logger.trace("beforeTestClass(): class [" + testClass + "]"); } getTestContext().updateState(null, null, null); for (TestExecutionListener testExecutionListener : getTestExecutionListeners()) { try { testExecutionListener.beforeTestClass(getTestContext()); } catch (Exception ex) { logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener + "] to process 'before class' callback for test class [" + testClass + "]", ex); throw ex; } } } /** * Hook for preparing a test instance prior to execution of any individual * test methods, for example for injecting dependencies, etc. Should be * called immediately after instantiation of the test instance. *

The managed {@link TestContext} will be updated with the supplied * testInstance. *

An attempt will be made to give each registered * {@link TestExecutionListener} a chance to prepare the test instance. If a * listener throws an exception, however, the remaining registered listeners * will not be called. * @param testInstance the test instance to prepare (never null) * @throws Exception if a registered TestExecutionListener throws an exception * @see #getTestExecutionListeners() */ public void prepareTestInstance(Object testInstance) throws Exception { Assert.notNull(testInstance, "testInstance must not be null"); if (logger.isTraceEnabled()) { logger.trace("prepareTestInstance(): instance [" + testInstance + "]"); } getTestContext().updateState(testInstance, null, null); for (TestExecutionListener testExecutionListener : getTestExecutionListeners()) { try { testExecutionListener.prepareTestInstance(getTestContext()); } catch (Exception ex) { logger.error("Caught exception while allowing TestExecutionListener [" + testExecutionListener + "] to prepare test instance [" + testInstance + "]", ex); throw ex; } } } /** * Hook for pre-processing a test before execution of the supplied * {@link Method test method}, for example for setting up test fixtures, * starting a transaction, etc. Should be called prior to any * framework-specific before methods (e.g., methods annotated with * JUnit's {@link org.junit.Before @Before}). *

The managed {@link TestContext} will be updated with the supplied * testInstance and testMethod. *

An attempt will be made to give each registered * {@link TestExecutionListener} a chance to pre-process the test method * execution. If a listener throws an exception, however, the remaining * registered listeners will not be called. * @param testInstance the current test instance (never null) * @param testMethod the test method which is about to be executed on the * test instance * @throws Exception if a registered TestExecutionListener throws an exception * @see #getTestExecutionListeners() */ public void beforeTestMethod(Object testInstance, Method testMethod) throws Exception { Assert.notNull(testInstance, "Test instance must not be null"); if (logger.isTraceEnabled()) { logger.trace("beforeTestMethod(): instance [" + testInstance + "], method [" + testMethod + "]"); } getTestContext().updateState(testInstance, testMethod, null); for (TestExecutionListener testExecutionListener : getTestExecutionListeners()) { try { testExecutionListener.beforeTestMethod(getTestContext()); } catch (Exception ex) { logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener + "] to process 'before' execution of test method [" + testMethod + "] for test instance [" + testInstance + "]", ex); throw ex; } } } /** * Hook for post-processing a test after execution of the supplied * {@link Method test method}, for example for tearing down test fixtures, * ending a transaction, etc. Should be called after any framework-specific * after methods (e.g., methods annotated with JUnit's * {@link org.junit.After @After}). *

The managed {@link TestContext} will be updated with the supplied * testInstance, testMethod, and * exception. *

Each registered {@link TestExecutionListener} will be given a chance to * post-process the test method execution. If a listener throws an * exception, the remaining registered listeners will still be called, but * the first exception thrown will be tracked and rethrown after all * listeners have executed. Note that registered listeners will be executed * in the opposite order in which they were registered. * @param testInstance the current test instance (never null) * @param testMethod the test method which has just been executed on the * test instance * @param exception the exception that was thrown during execution of the * test method or by a TestExecutionListener, or null if none * was thrown * @throws Exception if a registered TestExecutionListener throws an exception * @see #getTestExecutionListeners() */ public void afterTestMethod(Object testInstance, Method testMethod, Throwable exception) throws Exception { Assert.notNull(testInstance, "testInstance must not be null"); if (logger.isTraceEnabled()) { logger.trace("afterTestMethod(): instance [" + testInstance + "], method [" + testMethod + "], exception [" + exception + "]"); } getTestContext().updateState(testInstance, testMethod, exception); Exception afterTestMethodException = null; // Traverse the TestExecutionListeners in reverse order to ensure proper // "wrapper"-style execution of listeners. for (TestExecutionListener testExecutionListener : getReversedTestExecutionListeners()) { try { testExecutionListener.afterTestMethod(getTestContext()); } catch (Exception ex) { logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener + "] to process 'after' execution for test: method [" + testMethod + "], instance [" + testInstance + "], exception [" + exception + "]", ex); if (afterTestMethodException == null) { afterTestMethodException = ex; } } } if (afterTestMethodException != null) { throw afterTestMethodException; } } /** * Hook for post-processing a test class after execution of all * tests within the class. Should be called after any framework-specific * after class methods (e.g., methods annotated with JUnit's * {@link org.junit.AfterClass @AfterClass}). *

Each registered {@link TestExecutionListener} will be given a chance to * post-process the test class. If a listener throws an exception, the * remaining registered listeners will still be called, but the first * exception thrown will be tracked and rethrown after all listeners have * executed. Note that registered listeners will be executed in the opposite * order in which they were registered. * @throws Exception if a registered TestExecutionListener throws an exception * @see #getTestExecutionListeners() */ public void afterTestClass() throws Exception { final Class testClass = getTestContext().getTestClass(); if (logger.isTraceEnabled()) { logger.trace("afterTestClass(): class [" + testClass + "]"); } getTestContext().updateState(null, null, null); Exception afterTestClassException = null; // Traverse the TestExecutionListeners in reverse order to ensure proper // "wrapper"-style execution of listeners. for (TestExecutionListener testExecutionListener : getReversedTestExecutionListeners()) { try { testExecutionListener.afterTestClass(getTestContext()); } catch (Exception ex) { logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener + "] to process 'after class' callback for test class [" + testClass + "]", ex); if (afterTestClassException == null) { afterTestClassException = ex; } } } if (afterTestClassException != null) { throw afterTestClassException; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy