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 446bd58dfde..a2413b1b39f 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/main/java/org/springframework/test/context/TestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/TestExecutionListener.java index 74fe7fc401f..c2c71537006 100644 --- a/spring-test/src/main/java/org/springframework/test/context/TestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/TestExecutionListener.java @@ -55,6 +55,7 @@ package org.springframework.test.context; * @author Sam Brannen * @author Juergen Hoeller * @since 2.5 + * @see TestContextManager * @see org.springframework.test.context.support.AbstractTestExecutionListener */ public interface TestExecutionListener { @@ -70,6 +71,7 @@ public interface TestExecutionListener { * concrete classes as necessary. * @param testContext the test context for the test; never {@code null} * @throws Exception allows any exception to propagate + * @since 3.0 */ default void beforeTestClass(TestContext testContext) throws Exception { /* no-op */ @@ -90,34 +92,90 @@ public interface TestExecutionListener { } /** - * Pre-processes a test before execution of the + * Pre-processes a test before execution of before + * lifecycle callbacks of the underlying test framework — for example, + * by setting up test fixtures. + *
This method must be called immediately prior to + * framework-specific before lifecycle callbacks. 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 default implementation is empty. Can be overridden by + * concrete classes as necessary. + * @param testContext the test context in which the test method will be + * executed; never {@code null} + * @throws Exception allows any exception to propagate + * @see #afterTestMethod + * @see #beforeTestExecution + * @see #afterTestExecution + */ + default void beforeTestMethod(TestContext testContext) throws Exception { + /* no-op */ + } + + /** + * Pre-processes a test immediately before execution of the * {@link java.lang.reflect.Method test method} in the supplied - * {@link TestContext test context}, for example by setting up test - * fixtures. - *
This method should be called immediately prior to framework-specific + * {@link TestContext test context} — for example, for timing + * or logging purposes. + *
This method must be called after framework-specific * before lifecycle callbacks. *
The default implementation is empty. Can be overridden by * concrete classes as necessary. * @param testContext the test context in which the test method will be * executed; never {@code null} * @throws Exception allows any exception to propagate + * @since 5.0 + * @see #beforeTestMethod + * @see #afterTestMethod + * @see #afterTestExecution */ - default void beforeTestMethod(TestContext testContext) throws Exception { + default void beforeTestExecution(TestContext testContext) throws Exception { /* no-op */ } /** - * Post-processes a test after execution of the + * Post-processes a test immediately after execution of the * {@link java.lang.reflect.Method test method} in the supplied - * {@link TestContext test context}, for example by tearing down test - * fixtures. - *
This method should be called immediately after framework-specific + * {@link TestContext test context} — for example, for timing + * or logging purposes. + *
This method must be called before framework-specific * after lifecycle callbacks. *
The default implementation is empty. Can be overridden by * concrete classes as necessary. + * @param testContext the test context in which the test method will be + * executed; never {@code null} + * @throws Exception allows any exception to propagate + * @since 5.0 + * @see #beforeTestMethod + * @see #afterTestMethod + * @see #beforeTestExecution + */ + default void afterTestExecution(TestContext testContext) throws Exception { + /* no-op */ + } + + /** + * Post-processes a test after execution of after + * lifecycle callbacks of the underlying test framework — for example, + * by tearing down test fixtures. + *
This method must be called immediately after + * framework-specific after lifecycle callbacks. 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 default implementation is empty. Can be overridden by + * concrete classes as necessary. * @param testContext the test context in which the test method was * executed; never {@code null} * @throws Exception allows any exception to propagate + * @see #beforeTestMethod + * @see #beforeTestExecution + * @see #afterTestExecution */ default void afterTestMethod(TestContext testContext) throws Exception { /* no-op */ @@ -134,6 +192,7 @@ public interface TestExecutionListener { * concrete classes as necessary. * @param testContext the test context for the test; never {@code null} * @throws Exception allows any exception to propagate + * @since 3.0 */ default void afterTestClass(TestContext testContext) throws Exception { /* no-op */ diff --git a/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringExtension.java b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringExtension.java index a8a91f40687..8eef966a330 100644 --- a/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringExtension.java +++ b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringExtension.java @@ -23,8 +23,10 @@ import java.lang.reflect.Parameter; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.AfterTestExecutionCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; import org.junit.jupiter.api.extension.ContainerExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; @@ -54,7 +56,8 @@ import org.springframework.util.Assert; * @see org.springframework.test.context.TestContextManager */ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, TestInstancePostProcessor, - BeforeEachCallback, AfterEachCallback, ParameterResolver { + BeforeEachCallback, AfterEachCallback, BeforeTestExecutionCallback, AfterTestExecutionCallback, + ParameterResolver { /** * {@link Namespace} in which {@code TestContextManagers} are stored, keyed @@ -101,6 +104,27 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes getTestContextManager(context).beforeTestMethod(testInstance, testMethod); } + /** + * Delegates to {@link TestContextManager#beforeTestExecution}. + */ + @Override + public void beforeTestExecution(TestExtensionContext context) throws Exception { + Object testInstance = context.getTestInstance(); + Method testMethod = context.getTestMethod().get(); + getTestContextManager(context).beforeTestExecution(testInstance, testMethod); + } + + /** + * Delegates to {@link TestContextManager#afterTestExecution}. + */ + @Override + public void afterTestExecution(TestExtensionContext context) throws Exception { + Object testInstance = context.getTestInstance(); + Method testMethod = context.getTestMethod().get(); + Throwable testException = context.getTestException().orElse(null); + getTestContextManager(context).afterTestExecution(testInstance, testMethod, testException); + } + /** * Delegates to {@link TestContextManager#afterTestMethod}. */ diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java b/spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java index a0ed3db9c20..22fa1a136d2 100644 --- a/spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java +++ b/spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java @@ -42,8 +42,10 @@ import org.springframework.test.context.TestContextManager; import org.springframework.test.context.junit4.rules.SpringClassRule; import org.springframework.test.context.junit4.rules.SpringMethodRule; import org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks; +import org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks; import org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks; import org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks; +import org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks; import org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks; import org.springframework.test.context.junit4.statements.SpringFailOnTimeout; import org.springframework.test.context.junit4.statements.SpringRepeat; @@ -270,6 +272,9 @@ public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner { * Spring-specific timeouts in that the former execute in a separate * thread while the latter simply execute in the main thread (like regular * tests). + * @see #methodInvoker(FrameworkMethod, Object) + * @see #withBeforeTestExecutionCallbacks(FrameworkMethod, Object, Statement) + * @see #withAfterTestExecutionCallbacks(FrameworkMethod, Object, Statement) * @see #possiblyExpectingExceptions(FrameworkMethod, Object, Statement) * @see #withBefores(FrameworkMethod, Object, Statement) * @see #withAfters(FrameworkMethod, Object, Statement) @@ -293,6 +298,8 @@ public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner { } Statement statement = methodInvoker(frameworkMethod, testInstance); + statement = withBeforeTestExecutionCallbacks(frameworkMethod, testInstance, statement); + statement = withAfterTestExecutionCallbacks(frameworkMethod, testInstance, statement); statement = possiblyExpectingExceptions(frameworkMethod, testInstance, statement); statement = withBefores(frameworkMethod, testInstance, statement); statement = withAfters(frameworkMethod, testInstance, statement); @@ -404,6 +411,26 @@ public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner { return TestAnnotationUtils.getTimeout(frameworkMethod.getMethod()); } + /** + * Wrap the supplied {@link Statement} with a {@code RunBeforeTestExecutionCallbacks} + * statement, thus preserving the default functionality while adding support for the + * Spring TestContext Framework. + * @see RunBeforeTestExecutionCallbacks + */ + protected Statement withBeforeTestExecutionCallbacks(FrameworkMethod frameworkMethod, Object testInstance, Statement statement) { + return new RunBeforeTestExecutionCallbacks(statement, testInstance, frameworkMethod.getMethod(), getTestContextManager()); + } + + /** + * Wrap the supplied {@link Statement} with a {@code RunAfterTestExecutionCallbacks} + * statement, thus preserving the default functionality while adding support for the + * Spring TestContext Framework. + * @see RunAfterTestExecutionCallbacks + */ + protected Statement withAfterTestExecutionCallbacks(FrameworkMethod frameworkMethod, Object testInstance, Statement statement) { + return new RunAfterTestExecutionCallbacks(statement, testInstance, frameworkMethod.getMethod(), getTestContextManager()); + } + /** * Wrap the {@link Statement} returned by the parent implementation with a * {@code RunBeforeTestMethodCallbacks} statement, thus preserving the @@ -414,8 +441,7 @@ public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner { @Override protected Statement withBefores(FrameworkMethod frameworkMethod, Object testInstance, Statement statement) { Statement junitBefores = super.withBefores(frameworkMethod, testInstance, statement); - return new RunBeforeTestMethodCallbacks(junitBefores, testInstance, frameworkMethod.getMethod(), - getTestContextManager()); + return new RunBeforeTestMethodCallbacks(junitBefores, testInstance, frameworkMethod.getMethod(), getTestContextManager()); } /** @@ -428,8 +454,7 @@ public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner { @Override protected Statement withAfters(FrameworkMethod frameworkMethod, Object testInstance, Statement statement) { Statement junitAfters = super.withAfters(frameworkMethod, testInstance, statement); - return new RunAfterTestMethodCallbacks(junitAfters, testInstance, frameworkMethod.getMethod(), - getTestContextManager()); + return new RunAfterTestMethodCallbacks(junitAfters, testInstance, frameworkMethod.getMethod(), getTestContextManager()); } /** diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/rules/SpringMethodRule.java b/spring-test/src/main/java/org/springframework/test/context/junit4/rules/SpringMethodRule.java index 89a97418ac1..a8f78a0d06b 100644 --- a/spring-test/src/main/java/org/springframework/test/context/junit4/rules/SpringMethodRule.java +++ b/spring-test/src/main/java/org/springframework/test/context/junit4/rules/SpringMethodRule.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -37,7 +37,7 @@ import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; /** - * {@code SpringMethodRule} is a custom JUnit {@link MethodRule} that + * {@code SpringMethodRule} is a custom JUnit 4 {@link MethodRule} that * supports instance-level and method-level features of the * Spring TestContext Framework in standard JUnit tests by means * of the {@link TestContextManager} and associated support classes and @@ -82,6 +82,12 @@ import org.springframework.util.ReflectionUtils; * *
NOTE: As of Spring Framework 4.3, this class requires JUnit 4.12 or higher. * + *
WARNING: Due to the shortcomings of JUnit rules, the + * {@code SpringMethodRule} does not support the + * {@code beforeTestExecution()} and {@code afterTestExecution()} callbacks of the + * {@link org.springframework.test.context.TestExecutionListener TestExecutionListener} + * API. + * * @author Sam Brannen * @author Philippe Marschall * @since 4.2 diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunAfterTestExecutionCallbacks.java b/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunAfterTestExecutionCallbacks.java new file mode 100644 index 00000000000..818b1e8b3c3 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunAfterTestExecutionCallbacks.java @@ -0,0 +1,100 @@ +/* + * Copyright 2002-2016 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.junit4.statements; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import org.junit.runners.model.MultipleFailureException; +import org.junit.runners.model.Statement; +import org.springframework.test.context.TestContextManager; + +/** + * {@code RunAfterTestExecutionCallbacks} is a custom JUnit {@link Statement} + * which allows the Spring TestContext Framework to be plugged into the + * JUnit 4 execution chain by calling {@link TestContextManager#afterTestExecution + * afterTestExecution()} on the supplied {@link TestContextManager}. + * + *
NOTE: This class requires JUnit 4.9 or higher. + * + * @author Sam Brannen + * @since 5.0 + * @see #evaluate() + * @see RunBeforeTestExecutionCallbacks + */ +public class RunAfterTestExecutionCallbacks extends Statement { + + private final Statement next; + + private final Object testInstance; + + private final Method testMethod; + + private final TestContextManager testContextManager; + + + /** + * Construct a new {@code RunAfterTestExecutionCallbacks} statement. + * @param next the next {@code Statement} in the execution chain + * @param testInstance the current test instance (never {@code null}) + * @param testMethod the test method which has just been executed on the + * test instance + * @param testContextManager the TestContextManager upon which to call + * {@code afterTestExecution()} + */ + public RunAfterTestExecutionCallbacks(Statement next, Object testInstance, Method testMethod, + TestContextManager testContextManager) { + + this.next = next; + this.testInstance = testInstance; + this.testMethod = testMethod; + this.testContextManager = testContextManager; + } + + /** + * Evaluate the next {@link Statement} in the execution chain (typically an + * instance of {@link RunBeforeTestExecutionCallbacks}), catching any exceptions + * thrown, and then invoke {@link TestContextManager#afterTestExecution} supplying + * the first caught exception (if any). + *
If the invocation of {@code afterTestExecution()} throws an exception, that
+ * exception will also be tracked. Multiple exceptions will be combined into a
+ * {@link MultipleFailureException}.
+ */
+ @Override
+ public void evaluate() throws Throwable {
+ Throwable testException = null;
+ List Concrete subclasses:
* See: SPR-3960
+ * and SPR-4365.
+ *
+ * Indirectly, this class also verifies that all {@code TestExecutionListener}
+ * lifecycle callbacks are called.
+ *
+ * @author Sam Brannen
+ * @since 5.0
+ */
+class FailingBeforeAndAfterMethodsSpringExtensionTestCase {
+
+ private static Stream Indirectly, this class also verifies that all {@link TestExecutionListener}
- * lifecycle callbacks are called.
+ * See: SPR-3960.
*
- * As of Spring 3.0, this class also tests support for the new
- * {@link TestExecutionListener#beforeTestClass(TestContext) beforeTestClass()}
- * and {@link TestExecutionListener#afterTestClass(TestContext)
- * afterTestClass()} lifecycle callback methods.
+ * Indirectly, this class also verifies that all {@code TestExecutionListener}
+ * lifecycle callbacks are called.
*
* @author Sam Brannen
* @since 2.5
*/
@RunWith(Parameterized.class)
-public class FailingBeforeAndAfterMethodsJUnitTests {
+public class FailingBeforeAndAfterMethodsSpringRunnerTests {
protected final Class> clazz;
@@ -68,13 +62,15 @@ public class FailingBeforeAndAfterMethodsJUnitTests {
AlwaysFailingAfterTestClassTestCase.class.getSimpleName(),//
AlwaysFailingPrepareTestInstanceTestCase.class.getSimpleName(),//
AlwaysFailingBeforeTestMethodTestCase.class.getSimpleName(),//
+ AlwaysFailingBeforeTestExecutionTestCase.class.getSimpleName(), //
+ AlwaysFailingAfterTestExecutionTestCase.class.getSimpleName(), //
AlwaysFailingAfterTestMethodTestCase.class.getSimpleName(),//
FailingBeforeTransactionTestCase.class.getSimpleName(),//
FailingAfterTransactionTestCase.class.getSimpleName() //
};
}
- public FailingBeforeAndAfterMethodsJUnitTests(String testClassName) throws Exception {
+ public FailingBeforeAndAfterMethodsSpringRunnerTests(String testClassName) throws Exception {
this.clazz = ClassUtils.forName(getClass().getName() + "." + testClassName, getClass().getClassLoader());
}
@@ -93,7 +89,7 @@ public class FailingBeforeAndAfterMethodsJUnitTests {
// -------------------------------------------------------------------
- protected static class AlwaysFailingBeforeTestClassTestExecutionListener extends AbstractTestExecutionListener {
+ protected static class AlwaysFailingBeforeTestClassTestExecutionListener implements TestExecutionListener {
@Override
public void beforeTestClass(TestContext testContext) {
@@ -101,7 +97,7 @@ public class FailingBeforeAndAfterMethodsJUnitTests {
}
}
- protected static class AlwaysFailingAfterTestClassTestExecutionListener extends AbstractTestExecutionListener {
+ protected static class AlwaysFailingAfterTestClassTestExecutionListener implements TestExecutionListener {
@Override
public void afterTestClass(TestContext testContext) {
@@ -109,7 +105,7 @@ public class FailingBeforeAndAfterMethodsJUnitTests {
}
}
- protected static class AlwaysFailingPrepareTestInstanceTestExecutionListener extends AbstractTestExecutionListener {
+ protected static class AlwaysFailingPrepareTestInstanceTestExecutionListener implements TestExecutionListener {
@Override
public void prepareTestInstance(TestContext testContext) throws Exception {
@@ -117,7 +113,7 @@ public class FailingBeforeAndAfterMethodsJUnitTests {
}
}
- protected static class AlwaysFailingBeforeTestMethodTestExecutionListener extends AbstractTestExecutionListener {
+ protected static class AlwaysFailingBeforeTestMethodTestExecutionListener implements TestExecutionListener {
@Override
public void beforeTestMethod(TestContext testContext) {
@@ -125,7 +121,15 @@ public class FailingBeforeAndAfterMethodsJUnitTests {
}
}
- protected static class AlwaysFailingAfterTestMethodTestExecutionListener extends AbstractTestExecutionListener {
+ protected static class AlwaysFailingBeforeTestExecutionTestExecutionListener implements TestExecutionListener {
+
+ @Override
+ public void beforeTestExecution(TestContext testContext) {
+ fail("always failing beforeTestExecution()");
+ }
+ }
+
+ protected static class AlwaysFailingAfterTestMethodTestExecutionListener implements TestExecutionListener {
@Override
public void afterTestMethod(TestContext testContext) {
@@ -133,8 +137,15 @@ public class FailingBeforeAndAfterMethodsJUnitTests {
}
}
+ protected static class AlwaysFailingAfterTestExecutionTestExecutionListener implements TestExecutionListener {
+
+ @Override
+ public void afterTestExecution(TestContext testContext) {
+ fail("always failing afterTestExecution()");
+ }
+ }
+
@RunWith(SpringRunner.class)
- @TestExecutionListeners({})
public static abstract class BaseTestCase {
@Test
@@ -162,6 +173,16 @@ public class FailingBeforeAndAfterMethodsJUnitTests {
public static class AlwaysFailingBeforeTestMethodTestCase extends BaseTestCase {
}
+ @Ignore("TestCase classes are run manually by the enclosing test class")
+ @TestExecutionListeners(AlwaysFailingBeforeTestExecutionTestExecutionListener.class)
+ public static class AlwaysFailingBeforeTestExecutionTestCase extends BaseTestCase {
+ }
+
+ @Ignore("TestCase classes are run manually by the enclosing test class")
+ @TestExecutionListeners(AlwaysFailingAfterTestExecutionTestExecutionListener.class)
+ public static class AlwaysFailingAfterTestExecutionTestCase extends BaseTestCase {
+ }
+
@Ignore("TestCase classes are run manually by the enclosing test class")
@TestExecutionListeners(AlwaysFailingAfterTestMethodTestExecutionListener.class)
public static class AlwaysFailingAfterTestMethodTestCase extends BaseTestCase {
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/FailingBeforeAndAfterMethodsTestNGTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/FailingBeforeAndAfterMethodsTestNGTests.java
index 6ea6ce30162..bdb99b44d54 100644
--- a/spring-test/src/test/java/org/springframework/test/context/junit4/FailingBeforeAndAfterMethodsTestNGTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/FailingBeforeAndAfterMethodsTestNGTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -16,9 +16,6 @@
package org.springframework.test.context.junit4;
-import java.util.Arrays;
-import java.util.Collection;
-
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -28,7 +25,6 @@ import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.TestExecutionListener;
import org.springframework.test.context.TestExecutionListeners;
-import org.springframework.test.context.support.AbstractTestExecutionListener;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests;
import org.springframework.test.context.testng.TrackingTestNGTestListener;
@@ -41,25 +37,15 @@ import org.testng.TestNG;
import static org.junit.Assert.*;
/**
- *
- * JUnit 4 based integration test for verifying that 'before' and 'after'
+ * Integration tests which verify that 'before' and 'after'
* methods of {@link TestExecutionListener TestExecutionListeners} as well as
- * {@link BeforeTransaction @BeforeTransaction} and
- * {@link AfterTransaction @AfterTransaction} methods can fail a test in a
- * TestNG environment, as requested in SPR-3960.
- *
- * Indirectly, this class also verifies that all {@link TestExecutionListener}
+ * {@code @BeforeTransaction} and {@code @AfterTransaction} methods can fail
+ * tests in a TestNG environment.
+ *
+ * See: SPR-3960.
+ *
+ * Indirectly, this class also verifies that all {@code TestExecutionListener}
* lifecycle callbacks are called.
- *
- * As of Spring 3.0, this class also tests support for the new
- * {@link TestExecutionListener#beforeTestClass(TestContext) beforeTestClass()}
- * and {@link TestExecutionListener#afterTestClass(TestContext)
- * afterTestClass()} lifecycle callback methods.
- *
*