diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/InternalSpringJUnit4ClassRunner.java b/spring-test/src/main/java/org/springframework/test/context/junit4/InternalSpringJUnit4ClassRunner.java new file mode 100644 index 000000000000..61aca14e7735 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/junit4/InternalSpringJUnit4ClassRunner.java @@ -0,0 +1,447 @@ +/* + * Copyright 2002-2013 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; + +import java.lang.reflect.Method; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.internal.AssumptionViolatedException; +import org.junit.internal.runners.model.EachTestNotifier; +import org.junit.internal.runners.model.ReflectiveCallable; +import org.junit.internal.runners.statements.ExpectException; +import org.junit.internal.runners.statements.Fail; +import org.junit.internal.runners.statements.FailOnTimeout; +import org.junit.runner.Description; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.BlockJUnit4ClassRunner; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.InitializationError; +import org.junit.runners.model.Statement; +import org.springframework.test.annotation.ExpectedException; +import org.springframework.test.annotation.ProfileValueUtils; +import org.springframework.test.annotation.Repeat; +import org.springframework.test.annotation.Timed; +import org.springframework.test.context.TestContextManager; +import org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks; +import org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks; +import org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks; +import org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks; +import org.springframework.test.context.junit4.statements.SpringFailOnTimeout; +import org.springframework.test.context.junit4.statements.SpringRepeat; +import org.springframework.util.ReflectionUtils; + +/** + *
+ * {@code InternalSpringJUnit4ClassRunner} is where the real stuff happens. + * {@link SpringJUnit4ClassRunner} delegates either directly, or indirectly + * through {@link SpringJUnit4ParameterizedClassRunner}, to this class. + * + * @author Sam Brannen + * @author Juergen Hoeller + * @since 4.0 + * @see SpringJUnit4ClassRunner + */ +@SuppressWarnings("deprecation") +class InternalSpringJUnit4ClassRunner extends BlockJUnit4ClassRunner { + + private static final Log logger = LogFactory.getLog(InternalSpringJUnit4ClassRunner.class); + + private final TestContextManager testContextManager; + + + /** + * Constructs a new {@code InternalSpringJUnit4ClassRunner} and initializes a + * {@link TestContextManager} to provide Spring testing functionality to + * standard JUnit tests. + * @param clazz the test class to be run + * @see #createTestContextManager(Class) + */ + public InternalSpringJUnit4ClassRunner(Class> clazz) throws InitializationError { + super(clazz); + if (logger.isDebugEnabled()) { + logger.debug("SpringJUnit4ClassRunner constructor called with [" + clazz + "]."); + } + this.testContextManager = createTestContextManager(clazz); + } + + /** + * Creates a new {@link TestContextManager} for the supplied test class and + * the configured default {@code ContextLoader} class name. + * Can be overridden by subclasses. + * @param clazz the test class to be managed + * @see #getDefaultContextLoaderClassName(Class) + */ + protected TestContextManager createTestContextManager(Class> clazz) { + return new TestContextManager(clazz, getDefaultContextLoaderClassName(clazz)); + } + + /** + * Get the {@link TestContextManager} associated with this runner. + */ + protected final TestContextManager getTestContextManager() { + return this.testContextManager; + } + + /** + * Get the name of the default {@code ContextLoader} class to use for + * the supplied test class. The named class will be used if the test class + * does not explicitly declare a {@code ContextLoader} class via the + * {@code @ContextConfiguration} annotation. + *
The default implementation returns {@code null}, thus implying use + * of the standard default {@code ContextLoader} class name. + * Can be overridden by subclasses. + * @param clazz the test class + * @return {@code null} + */ + protected String getDefaultContextLoaderClassName(Class> clazz) { + return null; + } + + /** + * Returns a description suitable for an ignored test class if the test is + * disabled via {@code @IfProfileValue} at the class-level, and + * otherwise delegates to the parent implementation. + * @see ProfileValueUtils#isTestEnabledInThisEnvironment(Class) + */ + @Override + public Description getDescription() { + if (!ProfileValueUtils.isTestEnabledInThisEnvironment(getTestClass().getJavaClass())) { + return Description.createSuiteDescription(getTestClass().getJavaClass()); + } + return super.getDescription(); + } + + /** + * Check whether the test is enabled in the first place. This prevents + * classes with a non-matching {@code @IfProfileValue} annotation + * from running altogether, even skipping the execution of + * {@code prepareTestInstance()} {@code TestExecutionListener} + * methods. + * @see ProfileValueUtils#isTestEnabledInThisEnvironment(Class) + * @see org.springframework.test.annotation.IfProfileValue + * @see org.springframework.test.context.TestExecutionListener + */ + @Override + public void run(RunNotifier notifier) { + if (!ProfileValueUtils.isTestEnabledInThisEnvironment(getTestClass().getJavaClass())) { + notifier.fireTestIgnored(getDescription()); + return; + } + super.run(notifier); + } + + /** + * Wraps the {@link Statement} returned by the parent implementation with a + * {@link RunBeforeTestClassCallbacks} statement, thus preserving the + * default functionality but adding support for the Spring TestContext + * Framework. + * @see RunBeforeTestClassCallbacks + */ + @Override + protected Statement withBeforeClasses(Statement statement) { + Statement junitBeforeClasses = super.withBeforeClasses(statement); + return new RunBeforeTestClassCallbacks(junitBeforeClasses, getTestContextManager()); + } + + /** + * Wraps the {@link Statement} returned by the parent implementation with a + * {@link RunAfterTestClassCallbacks} statement, thus preserving the default + * functionality but adding support for the Spring TestContext Framework. + * @see RunAfterTestClassCallbacks + */ + @Override + protected Statement withAfterClasses(Statement statement) { + Statement junitAfterClasses = super.withAfterClasses(statement); + return new RunAfterTestClassCallbacks(junitAfterClasses, getTestContextManager()); + } + + /** + * Delegates to the parent implementation for creating the test instance and + * then allows the {@link #getTestContextManager() TestContextManager} to + * prepare the test instance before returning it. + * @see TestContextManager#prepareTestInstance(Object) + */ + @Override + protected Object createTest() throws Exception { + Object testInstance = super.createTest(); + getTestContextManager().prepareTestInstance(testInstance); + return testInstance; + } + + /** + * Performs the same logic as + * {@link BlockJUnit4ClassRunner#runChild(FrameworkMethod, RunNotifier)}, + * except that tests are determined to be ignored by + * {@link #isTestMethodIgnored(FrameworkMethod)}. + */ + @Override + protected void runChild(FrameworkMethod frameworkMethod, RunNotifier notifier) { + EachTestNotifier eachNotifier = springMakeNotifier(frameworkMethod, notifier); + if (isTestMethodIgnored(frameworkMethod)) { + eachNotifier.fireTestIgnored(); + return; + } + + eachNotifier.fireTestStarted(); + try { + methodBlock(frameworkMethod).evaluate(); + } + catch (AssumptionViolatedException e) { + eachNotifier.addFailedAssumption(e); + } + catch (Throwable e) { + eachNotifier.addFailure(e); + } + finally { + eachNotifier.fireTestFinished(); + } + } + + /** + * {@code springMakeNotifier()} is an exact copy of + * {@link BlockJUnit4ClassRunner BlockJUnit4ClassRunner's} + * {@code makeNotifier()} method, but we have decided to prefix it with + * "spring" and keep it {@code private} in order to avoid the + * compatibility clashes that were introduced in JUnit between versions 4.5, + * 4.6, and 4.7. + */ + private EachTestNotifier springMakeNotifier(FrameworkMethod method, RunNotifier notifier) { + Description description = describeChild(method); + return new EachTestNotifier(notifier, description); + } + + /** + * Augments the default JUnit behavior + * {@link #withPotentialRepeat(FrameworkMethod, Object, Statement) with + * potential repeats} of the entire execution chain. + *
Furthermore, support for timeouts has been moved down the execution chain + * in order to include execution of {@link org.junit.Before @Before} + * and {@link org.junit.After @After} methods within the timed + * execution. Note that this differs from the default JUnit behavior of + * executing {@code @Before} and {@code @After} methods + * in the main thread while executing the actual test method in a separate + * thread. Thus, the end effect is that {@code @Before} and + * {@code @After} methods will be executed in the same thread as + * the test method. As a consequence, JUnit-specified timeouts will work + * fine in combination with Spring transactions. Note that JUnit-specific + * timeouts still differ from 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 #possiblyExpectingExceptions(FrameworkMethod, Object, Statement) + * @see #withBefores(FrameworkMethod, Object, Statement) + * @see #withAfters(FrameworkMethod, Object, Statement) + * @see #withPotentialTimeout(FrameworkMethod, Object, Statement) + * @see #withPotentialRepeat(FrameworkMethod, Object, Statement) + */ + @Override + protected Statement methodBlock(FrameworkMethod frameworkMethod) { + Object testInstance; + try { + testInstance = new ReflectiveCallable() { + + @Override + protected Object runReflectiveCall() throws Throwable { + return createTest(); + } + }.run(); + } + catch (Throwable ex) { + return new Fail(ex); + } + + Statement statement = methodInvoker(frameworkMethod, testInstance); + statement = possiblyExpectingExceptions(frameworkMethod, testInstance, statement); + statement = withBefores(frameworkMethod, testInstance, statement); + statement = withAfters(frameworkMethod, testInstance, statement); + statement = withRulesReflectively(frameworkMethod, testInstance, statement); + statement = withPotentialRepeat(frameworkMethod, testInstance, statement); + statement = withPotentialTimeout(frameworkMethod, testInstance, statement); + + return statement; + } + + /** + * Invokes JUnit 4.7's private {@code withRules()} method using + * reflection. This is necessary for backwards compatibility with the JUnit + * 4.5 and 4.6 implementations of {@link BlockJUnit4ClassRunner}. + */ + private Statement withRulesReflectively(FrameworkMethod frameworkMethod, Object testInstance, Statement statement) { + Method withRulesMethod = ReflectionUtils.findMethod(getClass(), "withRules", FrameworkMethod.class, + Object.class, Statement.class); + if (withRulesMethod != null) { + // Original JUnit 4.7 code: + // statement = withRules(frameworkMethod, testInstance, statement); + ReflectionUtils.makeAccessible(withRulesMethod); + statement = (Statement) ReflectionUtils.invokeMethod(withRulesMethod, this, frameworkMethod, testInstance, + statement); + } + return statement; + } + + /** + * Returns {@code true} if {@link Ignore @Ignore} is present for + * the supplied {@link FrameworkMethod test method} or if the test method is + * disabled via {@code @IfProfileValue}. + * @see ProfileValueUtils#isTestEnabledInThisEnvironment(Method, Class) + */ + protected boolean isTestMethodIgnored(FrameworkMethod frameworkMethod) { + Method method = frameworkMethod.getMethod(); + return (method.isAnnotationPresent(Ignore.class) || !ProfileValueUtils.isTestEnabledInThisEnvironment(method, + getTestClass().getJavaClass())); + } + + /** + * Performs the same logic as + * {@link BlockJUnit4ClassRunner#possiblyExpectingExceptions(FrameworkMethod, Object, Statement)} + * except that the expected exception is retrieved using + * {@link #getExpectedException(FrameworkMethod)}. + */ + @Override + protected Statement possiblyExpectingExceptions(FrameworkMethod frameworkMethod, Object testInstance, Statement next) { + Class extends Throwable> expectedException = getExpectedException(frameworkMethod); + return expectedException != null ? new ExpectException(next, expectedException) : next; + } + + /** + * Get the {@code exception} that the supplied {@link FrameworkMethod + * test method} is expected to throw. + *
Supports both Spring's {@link ExpectedException @ExpectedException(...)} + * and JUnit's {@link Test#expected() @Test(expected=...)} annotations, but + * not both simultaneously. + * @return the expected exception, or {@code null} if none was specified + */ + protected Class extends Throwable> getExpectedException(FrameworkMethod frameworkMethod) { + Test testAnnotation = frameworkMethod.getAnnotation(Test.class); + Class extends Throwable> junitExpectedException = (testAnnotation != null + && testAnnotation.expected() != Test.None.class ? testAnnotation.expected() : null); + + ExpectedException expectedExAnn = frameworkMethod.getAnnotation(ExpectedException.class); + Class extends Throwable> springExpectedException = (expectedExAnn != null ? expectedExAnn.value() : null); + + if (springExpectedException != null && junitExpectedException != null) { + String msg = "Test method [" + frameworkMethod.getMethod() + + "] has been configured with Spring's @ExpectedException(" + springExpectedException.getName() + + ".class) and JUnit's @Test(expected=" + junitExpectedException.getName() + + ".class) annotations. " + + "Only one declaration of an 'expected exception' is permitted per test method."; + logger.error(msg); + throw new IllegalStateException(msg); + } + + return springExpectedException != null ? springExpectedException : junitExpectedException; + } + + /** + * Supports both Spring's {@link Timed @Timed} and JUnit's + * {@link Test#timeout() @Test(timeout=...)} annotations, but not both + * simultaneously. Returns either a {@link SpringFailOnTimeout}, a + * {@link FailOnTimeout}, or the unmodified, supplied {@link Statement} as + * appropriate. + * @see #getSpringTimeout(FrameworkMethod) + * @see #getJUnitTimeout(FrameworkMethod) + */ + @Override + protected Statement withPotentialTimeout(FrameworkMethod frameworkMethod, Object testInstance, Statement next) { + Statement statement = null; + long springTimeout = getSpringTimeout(frameworkMethod); + long junitTimeout = getJUnitTimeout(frameworkMethod); + if (springTimeout > 0 && junitTimeout > 0) { + String msg = "Test method [" + frameworkMethod.getMethod() + + "] has been configured with Spring's @Timed(millis=" + springTimeout + + ") and JUnit's @Test(timeout=" + junitTimeout + + ") annotations. Only one declaration of a 'timeout' is permitted per test method."; + logger.error(msg); + throw new IllegalStateException(msg); + } + else if (springTimeout > 0) { + statement = new SpringFailOnTimeout(next, springTimeout); + } + else if (junitTimeout > 0) { + statement = new FailOnTimeout(next, junitTimeout); + } + else { + statement = next; + } + + return statement; + } + + /** + * Retrieves the configured JUnit {@code timeout} from the {@link Test + * @Test} annotation on the supplied {@link FrameworkMethod test method}. + * @return the timeout, or {@code 0} if none was specified. + */ + protected long getJUnitTimeout(FrameworkMethod frameworkMethod) { + Test testAnnotation = frameworkMethod.getAnnotation(Test.class); + return (testAnnotation != null && testAnnotation.timeout() > 0 ? testAnnotation.timeout() : 0); + } + + /** + * Retrieves the configured Spring-specific {@code timeout} from the + * {@link Timed @Timed} annotation on the supplied + * {@link FrameworkMethod test method}. + * @return the timeout, or {@code 0} if none was specified. + */ + protected long getSpringTimeout(FrameworkMethod frameworkMethod) { + Timed timedAnnotation = frameworkMethod.getAnnotation(Timed.class); + return (timedAnnotation != null && timedAnnotation.millis() > 0 ? timedAnnotation.millis() : 0); + } + + /** + * Wraps the {@link Statement} returned by the parent implementation with a + * {@link RunBeforeTestMethodCallbacks} statement, thus preserving the + * default functionality but adding support for the Spring TestContext + * Framework. + * @see RunBeforeTestMethodCallbacks + */ + @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()); + } + + /** + * Wraps the {@link Statement} returned by the parent implementation with a + * {@link RunAfterTestMethodCallbacks} statement, thus preserving the + * default functionality but adding support for the Spring TestContext + * Framework. + * @see RunAfterTestMethodCallbacks + */ + @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()); + } + + /** + * Supports Spring's {@link Repeat @Repeat} annotation by returning a + * {@link SpringRepeat} statement initialized with the configured repeat + * count or {@code 1} if no repeat count is configured. + * @see SpringRepeat + */ + protected Statement withPotentialRepeat(FrameworkMethod frameworkMethod, Object testInstance, Statement next) { + Repeat repeatAnnotation = frameworkMethod.getAnnotation(Repeat.class); + int repeat = (repeatAnnotation != null ? repeatAnnotation.value() : 1); + return new SpringRepeat(next, frameworkMethod.getMethod(), repeat); + } + +} 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 5496b4ee6330..102628fab561 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 @@ -1,11 +1,11 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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 + * 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, @@ -16,48 +16,39 @@ package org.springframework.test.context.junit4; +import java.lang.annotation.Annotation; import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.junit.Ignore; import org.junit.Test; -import org.junit.internal.AssumptionViolatedException; -import org.junit.internal.runners.model.EachTestNotifier; -import org.junit.internal.runners.model.ReflectiveCallable; -import org.junit.internal.runners.statements.ExpectException; -import org.junit.internal.runners.statements.Fail; -import org.junit.internal.runners.statements.FailOnTimeout; import org.junit.runner.Description; +import org.junit.runner.Runner; +import org.junit.runner.manipulation.Filter; +import org.junit.runner.manipulation.Filterable; +import org.junit.runner.manipulation.NoTestsRemainException; +import org.junit.runner.manipulation.Sortable; +import org.junit.runner.manipulation.Sorter; import org.junit.runner.notification.RunNotifier; import org.junit.runners.BlockJUnit4ClassRunner; -import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.Parameterized.Parameters; import org.junit.runners.model.InitializationError; -import org.junit.runners.model.Statement; import org.springframework.test.annotation.ExpectedException; -import org.springframework.test.annotation.ProfileValueUtils; import org.springframework.test.annotation.Repeat; import org.springframework.test.annotation.Timed; import org.springframework.test.context.TestContextManager; -import org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks; -import org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks; -import org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks; -import org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks; -import org.springframework.test.context.junit4.statements.SpringFailOnTimeout; -import org.springframework.test.context.junit4.statements.SpringRepeat; -import org.springframework.util.ReflectionUtils; /** *
- * {@code SpringJUnit4ClassRunner} is a custom extension of - * {@link BlockJUnit4ClassRunner} which provides functionality of the - * Spring TestContext Framework to standard JUnit 4.5+ tests by means - * of the {@link TestContextManager} and associated support classes and - * annotations. + * {@code SpringJUnit4ClassRunner} is a custom extension of {@link BlockJUnit4ClassRunner} + * which provides functionality of the Spring TestContext Framework to standard + * JUnit 4.5+ tests by means of the {@link TestContextManager} and associated support + * classes and annotations. *
*- * The following list constitutes all annotations currently supported directly - * by {@code SpringJUnit4ClassRunner}. + * The following list constitutes all annotations currently supported directly by + * {@code SpringJUnit4ClassRunner}. * (Note that additional annotations may be supported by various * {@link org.springframework.test.context.TestExecutionListener * TestExecutionListeners}) @@ -69,406 +60,103 @@ *
- * NOTE: As of Spring 3.0, {@code SpringJUnit4ClassRunner} requires - * JUnit 4.5+. + * NOTE: As of Spring 3.0, {@code SpringJUnit4ClassRunner} requires JUnit 4.5+. *
- * + * * @author Sam Brannen * @author Juergen Hoeller + * @author Gaetan Pitteloud * @since 2.5 * @see TestContextManager */ -@SuppressWarnings("deprecation") -public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner { - - private static final Log logger = LogFactory.getLog(SpringJUnit4ClassRunner.class); +public class SpringJUnit4ClassRunner extends Runner implements Filterable, Sortable { - private final TestContextManager testContextManager; + private final Runner internalRunner; - - /** - * Constructs a new {@code SpringJUnit4ClassRunner} and initializes a - * {@link TestContextManager} to provide Spring testing functionality to - * standard JUnit tests. - * @param clazz the test class to be run - * @see #createTestContextManager(Class) - */ - public SpringJUnit4ClassRunner(Class> clazz) throws InitializationError { - super(clazz); - if (logger.isDebugEnabled()) { - logger.debug("SpringJUnit4ClassRunner constructor called with [" + clazz + "]."); + public SpringJUnit4ClassRunner(Class> testClass) throws InitializationError { + ListThe default implementation returns {@code null}, thus implying use - * of the standard default {@code ContextLoader} class name. - * Can be overridden by subclasses. - * @param clazz the test class - * @return {@code null} - */ - protected String getDefaultContextLoaderClassName(Class> clazz) { - return null; - } - - /** - * Returns a description suitable for an ignored test class if the test is - * disabled via {@code @IfProfileValue} at the class-level, and - * otherwise delegates to the parent implementation. - * @see ProfileValueUtils#isTestEnabledInThisEnvironment(Class) - */ @Override public Description getDescription() { - if (!ProfileValueUtils.isTestEnabledInThisEnvironment(getTestClass().getJavaClass())) { - return Description.createSuiteDescription(getTestClass().getJavaClass()); - } - return super.getDescription(); + return internalRunner.getDescription(); } - /** - * Check whether the test is enabled in the first place. This prevents - * classes with a non-matching {@code @IfProfileValue} annotation - * from running altogether, even skipping the execution of - * {@code prepareTestInstance()} {@code TestExecutionListener} - * methods. - * @see ProfileValueUtils#isTestEnabledInThisEnvironment(Class) - * @see org.springframework.test.annotation.IfProfileValue - * @see org.springframework.test.context.TestExecutionListener - */ @Override public void run(RunNotifier notifier) { - if (!ProfileValueUtils.isTestEnabledInThisEnvironment(getTestClass().getJavaClass())) { - notifier.fireTestIgnored(getDescription()); - return; - } - super.run(notifier); - } - - /** - * Wraps the {@link Statement} returned by the parent implementation with a - * {@link RunBeforeTestClassCallbacks} statement, thus preserving the - * default functionality but adding support for the Spring TestContext - * Framework. - * @see RunBeforeTestClassCallbacks - */ - @Override - protected Statement withBeforeClasses(Statement statement) { - Statement junitBeforeClasses = super.withBeforeClasses(statement); - return new RunBeforeTestClassCallbacks(junitBeforeClasses, getTestContextManager()); - } - - /** - * Wraps the {@link Statement} returned by the parent implementation with a - * {@link RunAfterTestClassCallbacks} statement, thus preserving the default - * functionality but adding support for the Spring TestContext Framework. - * @see RunAfterTestClassCallbacks - */ - @Override - protected Statement withAfterClasses(Statement statement) { - Statement junitAfterClasses = super.withAfterClasses(statement); - return new RunAfterTestClassCallbacks(junitAfterClasses, getTestContextManager()); + internalRunner.run(notifier); } - /** - * Delegates to the parent implementation for creating the test instance and - * then allows the {@link #getTestContextManager() TestContextManager} to - * prepare the test instance before returning it. - * @see TestContextManager#prepareTestInstance(Object) - */ - @Override - protected Object createTest() throws Exception { - Object testInstance = super.createTest(); - getTestContextManager().prepareTestInstance(testInstance); - return testInstance; - } - - /** - * Performs the same logic as - * {@link BlockJUnit4ClassRunner#runChild(FrameworkMethod, RunNotifier)}, - * except that tests are determined to be ignored by - * {@link #isTestMethodIgnored(FrameworkMethod)}. - */ - @Override - protected void runChild(FrameworkMethod frameworkMethod, RunNotifier notifier) { - EachTestNotifier eachNotifier = springMakeNotifier(frameworkMethod, notifier); - if (isTestMethodIgnored(frameworkMethod)) { - eachNotifier.fireTestIgnored(); - return; - } - - eachNotifier.fireTestStarted(); - try { - methodBlock(frameworkMethod).evaluate(); - } - catch (AssumptionViolatedException e) { - eachNotifier.addFailedAssumption(e); - } - catch (Throwable e) { - eachNotifier.addFailure(e); - } - finally { - eachNotifier.fireTestFinished(); - } + public void filter(Filter filter) throws NoTestsRemainException { + ((Filterable) internalRunner).filter(filter); } - /** - * {@code springMakeNotifier()} is an exact copy of - * {@link BlockJUnit4ClassRunner BlockJUnit4ClassRunner's} - * {@code makeNotifier()} method, but we have decided to prefix it with - * "spring" and keep it {@code private} in order to avoid the - * compatibility clashes that were introduced in JUnit between versions 4.5, - * 4.6, and 4.7. - */ - private EachTestNotifier springMakeNotifier(FrameworkMethod method, RunNotifier notifier) { - Description description = describeChild(method); - return new EachTestNotifier(notifier, description); + public void sort(Sorter sorter) { + ((Sortable) internalRunner).sort(sorter); } /** - * Augments the default JUnit behavior - * {@link #withPotentialRepeat(FrameworkMethod, Object, Statement) with - * potential repeats} of the entire execution chain. - *
Furthermore, support for timeouts has been moved down the execution chain
- * in order to include execution of {@link org.junit.Before @Before}
- * and {@link org.junit.After @After} methods within the timed
- * execution. Note that this differs from the default JUnit behavior of
- * executing {@code @Before} and {@code @After} methods
- * in the main thread while executing the actual test method in a separate
- * thread. Thus, the end effect is that {@code @Before} and
- * {@code @After} methods will be executed in the same thread as
- * the test method. As a consequence, JUnit-specified timeouts will work
- * fine in combination with Spring transactions. Note that JUnit-specific
- * timeouts still differ from 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 #possiblyExpectingExceptions(FrameworkMethod, Object, Statement)
- * @see #withBefores(FrameworkMethod, Object, Statement)
- * @see #withAfters(FrameworkMethod, Object, Statement)
- * @see #withPotentialTimeout(FrameworkMethod, Object, Statement)
- * @see #withPotentialRepeat(FrameworkMethod, Object, Statement)
+ * Gets all methods in the supplied {@link Class class} and its superclasses which are
+ * annotated with {@link Parameters}.
+ *
+ * @param clazz the class for which to retrieve the annotated methods
+ * @return all annotated methods in the supplied class and its superclasses
*/
- @Override
- protected Statement methodBlock(FrameworkMethod frameworkMethod) {
- Object testInstance;
- try {
- testInstance = new ReflectiveCallable() {
-
- @Override
- protected Object runReflectiveCall() throws Throwable {
- return createTest();
+ private List Supports both Spring's {@link ExpectedException @ExpectedException(...)}
- * and JUnit's {@link Test#expected() @Test(expected=...)} annotations, but
- * not both simultaneously.
- * @return the expected exception, or {@code null} if none was specified
+ * Gets all superclasses of the supplied {@link Class class}, including the class
+ * itself. The ordering of the returned list will begin with the supplied class and
+ * continue up the class hierarchy.
+ *
+ * Note: This code has been borrowed from
+ * {@link org.junit.internal.runners.TestClass#getSuperClasses(Class)} and adapted.
+ *
+ * @param clazz the class for which to retrieve the superclasses.
+ * @return all superclasses of the supplied class.
*/
- protected Class extends Throwable> getExpectedException(FrameworkMethod frameworkMethod) {
- Test testAnnotation = frameworkMethod.getAnnotation(Test.class);
- Class extends Throwable> junitExpectedException = (testAnnotation != null
- && testAnnotation.expected() != Test.None.class ? testAnnotation.expected() : null);
-
- ExpectedException expectedExAnn = frameworkMethod.getAnnotation(ExpectedException.class);
- Class extends Throwable> springExpectedException = (expectedExAnn != null ? expectedExAnn.value() : null);
-
- if (springExpectedException != null && junitExpectedException != null) {
- String msg = "Test method [" + frameworkMethod.getMethod()
- + "] has been configured with Spring's @ExpectedException(" + springExpectedException.getName()
- + ".class) and JUnit's @Test(expected=" + junitExpectedException.getName()
- + ".class) annotations. "
- + "Only one declaration of an 'expected exception' is permitted per test method.";
- logger.error(msg);
- throw new IllegalStateException(msg);
- }
-
- return springExpectedException != null ? springExpectedException : junitExpectedException;
- }
-
- /**
- * Supports both Spring's {@link Timed @Timed} and JUnit's
- * {@link Test#timeout() @Test(timeout=...)} annotations, but not both
- * simultaneously. Returns either a {@link SpringFailOnTimeout}, a
- * {@link FailOnTimeout}, or the unmodified, supplied {@link Statement} as
- * appropriate.
- * @see #getSpringTimeout(FrameworkMethod)
- * @see #getJUnitTimeout(FrameworkMethod)
- */
- @Override
- protected Statement withPotentialTimeout(FrameworkMethod frameworkMethod, Object testInstance, Statement next) {
- Statement statement = null;
- long springTimeout = getSpringTimeout(frameworkMethod);
- long junitTimeout = getJUnitTimeout(frameworkMethod);
- if (springTimeout > 0 && junitTimeout > 0) {
- String msg = "Test method [" + frameworkMethod.getMethod()
- + "] has been configured with Spring's @Timed(millis=" + springTimeout
- + ") and JUnit's @Test(timeout=" + junitTimeout
- + ") annotations. Only one declaration of a 'timeout' is permitted per test method.";
- logger.error(msg);
- throw new IllegalStateException(msg);
- }
- else if (springTimeout > 0) {
- statement = new SpringFailOnTimeout(next, springTimeout);
+ private List
+ * Borrowed from JUnit's {@code Parameterized} runner, and adapted to be able to run the
+ * test with a spring-aware runner. In particular, the TestContextManager is created once
+ * by this class, in order to be able to invoke beforeTestClass and afterTestClass methods
+ * once for every set of parameters.
+ *
+ * Invoked internally by the {@link SpringJUnit4ClassRunner}.
+ *
+ * @since 3.2
+ * @author Gaetan Pitteloud
+ */
+class SpringJUnit4ParameterizedClassRunner extends Suite {
+
+ private final ArrayList