diff --git a/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java b/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java index 446bd58dfd..a2413b1b39 100644 --- a/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java +++ b/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java @@ -40,23 +40,29 @@ import org.springframework.util.ReflectionUtils; *
Support for loading and accessing - * {@link org.springframework.context.ApplicationContext application contexts}, + * {@linkplain org.springframework.context.ApplicationContext application contexts}, * dependency injection of test instances, - * {@link org.springframework.transaction.annotation.Transactional transactional} + * {@linkplain org.springframework.transaction.annotation.Transactional transactional} * execution of test methods, etc. is provided by * {@link SmartContextLoader ContextLoaders} and {@link TestExecutionListener * TestExecutionListeners}, which are configured via @@ -173,7 +179,7 @@ public class TestContextManager { /** * 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 + * before class methods (e.g., methods annotated with JUnit 4'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 @@ -181,6 +187,7 @@ public class TestContextManager { * registered listeners will not be called. * @throws Exception if a registered TestExecutionListener throws an * exception + * @since 3.0 * @see #getTestExecutionListeners() */ public void beforeTestClass() throws Exception { @@ -195,10 +202,7 @@ public class TestContextManager { testExecutionListener.beforeTestClass(getTestContext()); } catch (Throwable ex) { - if (logger.isWarnEnabled()) { - logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener + - "] to process 'before class' callback for test class [" + testClass + "]", ex); - } + logException(ex, "beforeTestClass", testExecutionListener, testClass); ReflectionUtils.rethrowException(ex); } } @@ -240,60 +244,101 @@ public class TestContextManager { } /** - * 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}). + * Hook for pre-processing a test before execution of before + * lifecycle callbacks of the underlying test framework — for example, + * setting up test fixtures, starting a transaction, etc. + *
This method must be called immediately prior to + * framework-specific before lifecycle callbacks (e.g., methods + * annotated with JUnit 4's {@link org.junit.Before @Before}). For historical + * reasons, this method is named {@code beforeTestMethod}. Since the + * introduction of {@link #beforeTestExecution}, a more suitable name for + * this method might be something like {@code beforeTestSetUp} or + * {@code beforeEach}; however, it is unfortunately impossible to rename + * this method due to backward compatibility concerns. *
The managed {@link TestContext} will be updated with the supplied * {@code testInstance} and {@code 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. + * {@link TestExecutionListener} a chance to perform its pre-processing. + * If a listener throws an exception, however, the remaining registered + * listeners will not be called. * @param testInstance the current test instance (never {@code 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 #afterTestMethod + * @see #beforeTestExecution + * @see #afterTestExecution * @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); + String callbackName = "beforeTestMethod"; + prepareForBeforeCallback(callbackName, testInstance, testMethod); for (TestExecutionListener testExecutionListener : getTestExecutionListeners()) { try { testExecutionListener.beforeTestMethod(getTestContext()); } catch (Throwable ex) { - if (logger.isWarnEnabled()) { - logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener + - "] to process 'before' execution of test method [" + testMethod + "] for test instance [" + - testInstance + "]", ex); - } - ReflectionUtils.rethrowException(ex); + handleBeforeException(ex, callbackName, testExecutionListener, testInstance, testMethod); + } + } + } + + /** + * Hook for pre-processing a test immediately before execution of + * the {@linkplain java.lang.reflect.Method test method} in the supplied + * {@linkplain TestContext test context} — for example, for timing + * or logging purposes. + *
This method must be called after framework-specific + * before lifecycle callbacks (e.g., methods annotated with JUnit 4's + * {@link org.junit.Before @Before}). + *
The managed {@link TestContext} will be updated with the supplied + * {@code testInstance} and {@code testMethod}. + *
An attempt will be made to give each registered + * {@link TestExecutionListener} a chance to perform its pre-processing. + * If a listener throws an exception, however, the remaining registered + * listeners will not be called. + * @param testInstance the current test instance (never {@code 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 + * @since 5.0 + * @see #beforeTestMethod + * @see #afterTestMethod + * @see #beforeTestExecution + * @see #afterTestExecution + * @see #getTestExecutionListeners() + */ + public void beforeTestExecution(Object testInstance, Method testMethod) throws Exception { + String callbackName = "beforeTestExecution"; + prepareForBeforeCallback(callbackName, testInstance, testMethod); + + for (TestExecutionListener testExecutionListener : getTestExecutionListeners()) { + try { + testExecutionListener.beforeTestExecution(getTestContext()); + } + catch (Throwable ex) { + handleBeforeException(ex, callbackName, testExecutionListener, testInstance, testMethod); } } } /** - * 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 + * Hook for post-processing a test immediately after execution of + * the {@linkplain java.lang.reflect.Method test method} in the supplied + * {@linkplain TestContext test context} — for example, for timing + * or logging purposes. + *
This method must be called before framework-specific + * after lifecycle callbacks (e.g., methods annotated with JUnit 4's * {@link org.junit.After @After}). *
The managed {@link TestContext} will be updated with the supplied - * {@code testInstance}, {@code testMethod}, and - * {@code 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. + * {@code testInstance}, {@code testMethod}, and {@code exception}. + *
Each registered {@link TestExecutionListener} will be given a chance + * to perform its post-processing. 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 {@code null}) * @param testMethod the test method which has just been executed on the * test instance @@ -301,15 +346,70 @@ public class TestContextManager { * test method or by a TestExecutionListener, or {@code null} if none * was thrown * @throws Exception if a registered TestExecutionListener throws an exception + * @since 5.0 + * @see #beforeTestMethod + * @see #afterTestMethod + * @see #beforeTestExecution * @see #getTestExecutionListeners() */ - public void afterTestMethod(Object testInstance, Method testMethod, Throwable exception) throws Exception { - Assert.notNull(testInstance, "Test instance must not be null"); - if (logger.isTraceEnabled()) { - logger.trace("afterTestMethod(): instance [" + testInstance + "], method [" + testMethod + - "], exception [" + exception + "]"); + public void afterTestExecution(Object testInstance, Method testMethod, Throwable exception) throws Exception { + String callbackName = "afterTestExecution"; + prepareForAfterCallback(callbackName, testInstance, testMethod, exception); + + Throwable afterTestExecutionException = null; + // Traverse the TestExecutionListeners in reverse order to ensure proper + // "wrapper"-style execution of listeners. + for (TestExecutionListener testExecutionListener : getReversedTestExecutionListeners()) { + try { + testExecutionListener.afterTestExecution(getTestContext()); + } + catch (Throwable ex) { + logException(ex, callbackName, testExecutionListener, testInstance, testMethod); + if (afterTestExecutionException == null) { + afterTestExecutionException = ex; + } + } } - getTestContext().updateState(testInstance, testMethod, exception); + if (afterTestExecutionException != null) { + ReflectionUtils.rethrowException(afterTestExecutionException); + } + } + + /** + * Hook for post-processing a test after execution of after + * lifecycle callbacks of the underlying test framework — for example, + * tearing down test fixtures, ending a transaction, etc. + *
This method must be called immediately after + * framework-specific after lifecycle callbacks (e.g., methods + * annotated with JUnit 4's {@link org.junit.After @After}). For historical + * reasons, this method is named {@code afterTestMethod}. Since the + * introduction of {@link #afterTestExecution}, a more suitable name for + * this method might be something like {@code afterTestTearDown} or + * {@code afterEach}; however, it is unfortunately impossible to rename + * this method due to backward compatibility concerns. + *
The managed {@link TestContext} will be updated with the supplied + * {@code testInstance}, {@code testMethod}, and {@code exception}. + *
Each registered {@link TestExecutionListener} will be given a chance + * to perform its post-processing. 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 {@code 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 {@code null} if none + * was thrown + * @throws Exception if a registered TestExecutionListener throws an exception + * @see #beforeTestMethod + * @see #beforeTestExecution + * @see #afterTestExecution + * @see #getTestExecutionListeners() + */ + public void afterTestMethod(Object testInstance, Method testMethod, Throwable exception) throws Exception { + String callbackName = "afterTestMethod"; + prepareForAfterCallback(callbackName, testInstance, testMethod, exception); Throwable afterTestMethodException = null; // Traverse the TestExecutionListeners in reverse order to ensure proper @@ -319,11 +419,7 @@ public class TestContextManager { testExecutionListener.afterTestMethod(getTestContext()); } catch (Throwable ex) { - if (logger.isWarnEnabled()) { - logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener + - "] to process 'after' execution for test: method [" + testMethod + "], instance [" + - testInstance + "], exception [" + exception + "]", ex); - } + logException(ex, callbackName, testExecutionListener, testInstance, testMethod); if (afterTestMethodException == null) { afterTestMethodException = ex; } @@ -337,7 +433,7 @@ public class TestContextManager { /** * 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 + * after class methods (e.g., methods annotated with JUnit 4'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
@@ -346,6 +442,7 @@ public class TestContextManager {
* 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
+ * @since 3.0
* @see #getTestExecutionListeners()
*/
public void afterTestClass() throws Exception {
@@ -363,10 +460,7 @@ public class TestContextManager {
testExecutionListener.afterTestClass(getTestContext());
}
catch (Throwable ex) {
- if (logger.isWarnEnabled()) {
- logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener +
- "] to process 'after class' callback for test class [" + testClass + "]", ex);
- }
+ logException(ex, "afterTestClass", testExecutionListener, testClass);
if (afterTestClassException == null) {
afterTestClassException = ex;
}
@@ -377,4 +471,46 @@ public class TestContextManager {
}
}
+ private void prepareForBeforeCallback(String callbackName, Object testInstance, Method testMethod) {
+ Assert.notNull(testInstance, "Test instance must not be null");
+ if (logger.isTraceEnabled()) {
+ logger.trace(String.format("%s(): instance [%s], method [%s]", callbackName, testInstance, testMethod));
+ }
+ getTestContext().updateState(testInstance, testMethod, null);
+ }
+
+ private void prepareForAfterCallback(String callbackName, Object testInstance, Method testMethod,
+ Throwable exception) {
+ Assert.notNull(testInstance, "Test instance must not be null");
+ if (logger.isTraceEnabled()) {
+ logger.trace(String.format("%s(): instance [%s], method [%s], exception [%s]", callbackName, testInstance,
+ testMethod, exception));
+ }
+ getTestContext().updateState(testInstance, testMethod, exception);
+ }
+
+ private void handleBeforeException(Throwable ex, String callbackName, TestExecutionListener testExecutionListener,
+ Object testInstance, Method testMethod) throws Exception {
+ logException(ex, callbackName, testExecutionListener, testInstance, testMethod);
+ ReflectionUtils.rethrowException(ex);
+ }
+
+ private void logException(Throwable ex, String callbackName, TestExecutionListener testExecutionListener,
+ Class> testClass) {
+ if (logger.isWarnEnabled()) {
+ logger.warn(String.format("Caught exception while invoking '%s' callback on " +
+ "TestExecutionListener [%s] for test class [%s]", callbackName, testExecutionListener,
+ testClass), ex);
+ }
+ }
+
+ private void logException(Throwable ex, String callbackName, TestExecutionListener testExecutionListener,
+ Object testInstance, Method testMethod) {
+ if (logger.isWarnEnabled()) {
+ logger.warn(String.format("Caught exception while invoking '%s' callback on " +
+ "TestExecutionListener [%s] for test method [%s] and test instance [%s]",
+ callbackName, testExecutionListener, testMethod, testInstance), ex);
+ }
+ }
+
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/TestContextManagerTests.java b/spring-test/src/test/java/org/springframework/test/context/TestContextManagerTests.java
index 3070ba4900..fb88f398a6 100644
--- a/spring-test/src/test/java/org/springframework/test/context/TestContextManagerTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/TestContextManagerTests.java
@@ -21,17 +21,8 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
import org.junit.Test;
-import org.springframework.core.style.ToStringCreator;
-import org.springframework.test.context.support.AbstractTestExecutionListener;
-
import static org.junit.Assert.*;
/**
@@ -44,94 +35,77 @@ import static org.junit.Assert.*;
*/
public class TestContextManagerTests {
- private static final String FIRST = "veni";
- private static final String SECOND = "vidi";
- private static final String THIRD = "vici";
-
- private static final List