From da893328407766d95cbaddf0ce99fcdfeb202bae Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sat, 9 Jul 2016 17:57:55 +0200 Subject: [PATCH] Introduce before/after test execution support in SpringJUnit4ClassRunner Issue: SPR-4365 --- .../junit4/SpringJUnit4ClassRunner.java | 33 +++++- .../junit4/rules/SpringMethodRule.java | 10 +- .../RunAfterTestExecutionCallbacks.java | 100 ++++++++++++++++++ .../RunBeforeTestExecutionCallbacks.java | 76 +++++++++++++ ...foreAndAfterMethodsSpringRunnerTests.java} | 61 +++++++---- ...gBeforeAndAfterMethodsSpringRuleTests.java | 7 +- 6 files changed, 257 insertions(+), 30 deletions(-) create mode 100644 spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunAfterTestExecutionCallbacks.java create mode 100644 spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunBeforeTestExecutionCallbacks.java rename spring-test/src/test/java/org/springframework/test/context/junit4/{FailingBeforeAndAfterMethodsJUnitTests.java => FailingBeforeAndAfterMethodsSpringRunnerTests.java} (76%) 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 a0ed3db9c2..22fa1a136d 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 89a97418ac..a8f78a0d06 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 0000000000..818b1e8b3c --- /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 errors = new ArrayList<>(); + try { + this.next.evaluate(); + } + catch (Throwable ex) { + testException = ex; + errors.add(ex); + } + + try { + this.testContextManager.afterTestExecution(this.testInstance, this.testMethod, testException); + } + catch (Throwable ex) { + errors.add(ex); + } + + MultipleFailureException.assertEmpty(errors); + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunBeforeTestExecutionCallbacks.java b/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunBeforeTestExecutionCallbacks.java new file mode 100644 index 0000000000..338ec27ed2 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunBeforeTestExecutionCallbacks.java @@ -0,0 +1,76 @@ +/* + * 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 org.junit.runners.model.Statement; +import org.springframework.test.context.TestContextManager; + +/** + * {@code RunBeforeTestExecutionCallbacks} 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#beforeTestExecution + * beforeTestExecution()} on the supplied {@link TestContextManager}. + * + * @author Sam Brannen + * @since 5.0 + * @see #evaluate() + * @see RunAfterTestExecutionCallbacks + */ +public class RunBeforeTestExecutionCallbacks extends Statement { + + private final Statement next; + + private final Object testInstance; + + private final Method testMethod; + + private final TestContextManager testContextManager; + + + /** + * Construct a new {@code RunBeforeTestExecutionCallbacks} 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 is about to be executed on the + * test instance + * @param testContextManager the TestContextManager upon which to call + * {@code beforeTestExecution()} + */ + public RunBeforeTestExecutionCallbacks(Statement next, Object testInstance, Method testMethod, + TestContextManager testContextManager) { + + this.next = next; + this.testInstance = testInstance; + this.testMethod = testMethod; + this.testContextManager = testContextManager; + } + + /** + * Invoke {@link TestContextManager#beforeTestExecution(Object, Method)} + * and then evaluate the next {@link Statement} in the execution chain + * (typically an instance of + * {@link org.junit.internal.runners.statements.InvokeMethod InvokeMethod}). + */ + @Override + public void evaluate() throws Throwable { + this.testContextManager.beforeTestExecution(this.testInstance, this.testMethod); + this.next.evaluate(); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/FailingBeforeAndAfterMethodsJUnitTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/FailingBeforeAndAfterMethodsSpringRunnerTests.java similarity index 76% rename from spring-test/src/test/java/org/springframework/test/context/junit4/FailingBeforeAndAfterMethodsJUnitTests.java rename to spring-test/src/test/java/org/springframework/test/context/junit4/FailingBeforeAndAfterMethodsSpringRunnerTests.java index 4918606f64..a2282a7a1a 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/FailingBeforeAndAfterMethodsJUnitTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/FailingBeforeAndAfterMethodsSpringRunnerTests.java @@ -27,7 +27,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.transaction.AfterTransaction; import org.springframework.test.context.transaction.BeforeTransaction; import org.springframework.transaction.annotation.Transactional; @@ -37,26 +36,21 @@ import static org.junit.Assert.*; import static org.springframework.test.context.junit4.JUnitTestingUtils.*; /** - * 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 - * JUnit environment, as requested in - * SPR-3960. + * {@code @BeforeTransaction} and {@code @AfterTransaction} methods can fail + * tests run via the {@link SpringRunner} in a JUnit 4 environment. * - *

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/rules/FailingBeforeAndAfterMethodsSpringRuleTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/rules/FailingBeforeAndAfterMethodsSpringRuleTests.java index aae85f1c26..d3838cb1ac 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/rules/FailingBeforeAndAfterMethodsSpringRuleTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/rules/FailingBeforeAndAfterMethodsSpringRuleTests.java @@ -27,7 +27,7 @@ import org.junit.runners.Parameterized.Parameters; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestExecutionListeners; -import org.springframework.test.context.junit4.FailingBeforeAndAfterMethodsJUnitTests; +import org.springframework.test.context.junit4.FailingBeforeAndAfterMethodsSpringRunnerTests; import org.springframework.test.context.transaction.AfterTransaction; import org.springframework.test.context.transaction.BeforeTransaction; import org.springframework.transaction.annotation.Transactional; @@ -35,14 +35,14 @@ import org.springframework.transaction.annotation.Transactional; import static org.junit.Assert.*; /** - * This class is an extension of {@link FailingBeforeAndAfterMethodsJUnitTests} + * This class is an extension of {@link FailingBeforeAndAfterMethodsSpringRunnerTests} * that has been modified to use {@link SpringClassRule} and * {@link SpringMethodRule}. * * @author Sam Brannen * @since 4.2 */ -public class FailingBeforeAndAfterMethodsSpringRuleTests extends FailingBeforeAndAfterMethodsJUnitTests { +public class FailingBeforeAndAfterMethodsSpringRuleTests extends FailingBeforeAndAfterMethodsSpringRunnerTests { @Parameters(name = "{0}") public static Object[] testData() { @@ -69,7 +69,6 @@ public class FailingBeforeAndAfterMethodsSpringRuleTests extends FailingBeforeAn // All tests are in superclass. @RunWith(JUnit4.class) - @TestExecutionListeners({}) public static abstract class BaseSpringRuleTestCase { @ClassRule -- GitLab