Browse Source

[SPR-5145] Completed migration to JUnit 4.5: SpringJUnit4ClassRunner now extends BlockJUnit4ClassRunner and uses custom Statements; removed obsolete SpringMethodRoadie and SpringTestMethod classes.

git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@1073 50f2f4bb-b051-0410-bef5-90022cba6387
pull/1/head
Sam Brannen 17 years ago
parent
commit
24e41d8822
  1. 368
      org.springframework.test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java
  2. 354
      org.springframework.test/src/main/java/org/springframework/test/context/junit4/SpringMethodRoadie.java
  3. 183
      org.springframework.test/src/main/java/org/springframework/test/context/junit4/SpringTestMethod.java
  4. 2
      org.springframework.test/src/main/java/org/springframework/test/context/junit4/package.html

368
org.springframework.test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java

@ -16,41 +16,56 @@ @@ -16,41 +16,56 @@
package org.springframework.test.context.junit4;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.internal.runners.InitializationError;
import org.junit.internal.runners.JUnit4ClassRunner;
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.Failure;
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.RunSpringTestContextAfters;
import org.springframework.test.context.junit4.statements.RunSpringTestContextBefores;
import org.springframework.test.context.junit4.statements.SpringFailOnTimeout;
import org.springframework.test.context.junit4.statements.SpringRepeat;
/**
* <p>
* SpringJUnit4ClassRunner is a custom extension of {@link JUnit4ClassRunner}
* which provides functionality of the <em>Spring TestContext Framework</em> to
* standard JUnit 4.5+ tests by means of the {@link TestContextManager} and
* associated support classes and annotations.
* <code>SpringJUnit4ClassRunner</code> is a custom extension of
* {@link BlockJUnit4ClassRunner} which provides functionality of the
* <em>Spring TestContext Framework</em> to standard JUnit 4.5+ tests by means
* of the {@link TestContextManager} and associated support classes and
* annotations.
* </p>
* <p>
* The following list constitutes all annotations currently supported directly
* by SpringJUnit4ClassRunner. <em>(Note that additional annotations
* may be supported by various
* by <code>SpringJUnit4ClassRunner</code>.
* <em>(Note that additional annotations may be supported by various
* {@link org.springframework.test.context.TestExecutionListener
* TestExecutionListeners})</em>
* </p>
* <ul>
* <li>{@link org.junit.Test#expected() &#064;Test(expected=...)}</li>
* <li>{@link org.springframework.test.annotation.ExpectedException
* &#064;ExpectedException}</li>
* <li>{@link org.junit.Test#timeout() &#064;Test(timeout=...)}</li>
* <li>{@link org.springframework.test.annotation.Timed &#064;Timed}</li>
* <li>{@link org.springframework.test.annotation.Repeat &#064;Repeat}</li>
* <li>{@link org.junit.Ignore &#064;Ignore}</li>
* <li>{@link Test#expected() &#064;Test(expected=...)}</li>
* <li>{@link ExpectedException &#064;ExpectedException}</li>
* <li>{@link Test#timeout() &#064;Test(timeout=...)}</li>
* <li>{@link Timed &#064;Timed}</li>
* <li>{@link Repeat &#064;Repeat}</li>
* <li>{@link Ignore &#064;Ignore}</li>
* <li>
* {@link org.springframework.test.annotation.ProfileValueSourceConfiguration
* &#064;ProfileValueSourceConfiguration}</li>
@ -58,9 +73,8 @@ import org.springframework.test.context.TestContextManager; @@ -58,9 +73,8 @@ import org.springframework.test.context.TestContextManager;
* &#064;IfProfileValue}</li>
* </ul>
* <p>
* <b>NOTE:</b> As of Spring 3.0 M1, SpringJUnit4ClassRunner requires JUnit 4.5,
* while internally still being based on JUnit 4.4 SPI. This will be rewritten
* based on JUnit 4.5's BlockJUnit4ClassRunner in a later Spring 3.0 release.
* <b>NOTE:</b> As of Spring 3.0, <code>SpringJUnit4ClassRunner</code> requires
* JUnit 4.5.
* </p>
*
* @author Sam Brannen
@ -68,7 +82,7 @@ import org.springframework.test.context.TestContextManager; @@ -68,7 +82,7 @@ import org.springframework.test.context.TestContextManager;
* @since 2.5
* @see TestContextManager
*/
public class SpringJUnit4ClassRunner extends JUnit4ClassRunner {
public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner {
private static final Log logger = LogFactory.getLog(SpringJUnit4ClassRunner.class);
@ -91,12 +105,61 @@ public class SpringJUnit4ClassRunner extends JUnit4ClassRunner { @@ -91,12 +105,61 @@ public class SpringJUnit4ClassRunner extends JUnit4ClassRunner {
this.testContextManager = createTestContextManager(clazz);
}
/**
* Creates a new {@link TestContextManager}. Can be overridden by
* subclasses.
*
* @param clazz the Class object corresponding to the test class to be
* managed
*/
protected TestContextManager createTestContextManager(Class<?> clazz) {
return new TestContextManager(clazz);
}
/**
* Get the {@link TestContextManager} associated with this runner.
*/
protected final TestContextManager getTestContextManager() {
return this.testContextManager;
}
/**
* 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;
}
/**
* Returns a description suitable for an ignored test class if the test is
* disabled via <code>&#064;IfProfileValue</code> 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>&#064;IfProfileValue</code> annotation
* from running altogether, even skipping the execution of
* <code>prepareTestInstance</code> listener methods.
* <code>prepareTestInstance()</code> <code>TestExecutionListener</code>
* methods.
*
* @see ProfileValueUtils#isTestEnabledInThisEnvironment(Class)
* @see org.springframework.test.annotation.IfProfileValue
* @see org.springframework.test.context.TestExecutionListener
*/
@ -110,71 +173,250 @@ public class SpringJUnit4ClassRunner extends JUnit4ClassRunner { @@ -110,71 +173,250 @@ public class SpringJUnit4ClassRunner extends JUnit4ClassRunner {
}
/**
* Delegates to {@link JUnit4ClassRunner#createTest()} to create the test
* instance and then to a {@link TestContextManager} to
* {@link TestContextManager#prepareTestInstance(Object) prepare} the test
* instance for Spring testing functionality.
* Performs the same logic as
* {@link BlockJUnit4ClassRunner#runChild(FrameworkMethod, RunNotifier)},
* except that tests are determined to be <em>ignored</em> by
* {@link #isTestMethodIgnored(FrameworkMethod)}.
*/
@Override
protected void runChild(FrameworkMethod frameworkMethod, RunNotifier notifier) {
EachTestNotifier eachNotifier = makeNotifier(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();
}
}
private EachTestNotifier makeNotifier(FrameworkMethod frameworkMethod, RunNotifier notifier) {
Description description = describeChild(frameworkMethod);
return new EachTestNotifier(notifier, description);
}
/**
* Augments the default JUnit behavior
* {@link #withPotentialRepeat(FrameworkMethod, Object, Statement) with
* potential repeats} of the entire execution chain.
* <p>
* Furthermore, support for timeouts has been moved down the execution chain
* in order to include execution of {@link org.junit.Before &#064;Before}
* and {@link org.junit.After &#064;After} methods within the timed
* execution. Note that this differs from the default JUnit behavior of
* executing <code>&#064;Before</code> and <code>&#064;After</code> methods
* in the main thread while executing the actual test method in a separate
* thread. Thus, the end effect is that <code>&#064;Before</code> and
* <code>&#064;After</code> 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).
* </p>
*
* @see JUnit4ClassRunner#createTest()
* @see TestContextManager#prepareTestInstance(Object)
* @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 Object createTest() throws Exception {
Object testInstance = super.createTest();
getTestContextManager().prepareTestInstance(testInstance);
return testInstance;
protected Statement methodBlock(FrameworkMethod frameworkMethod) {
Object testInstance;
try {
testInstance = new ReflectiveCallable() {
@Override
protected Object runReflectiveCall() throws Throwable {
return createTest();
}
}.run();
}
catch (Throwable e) {
return new Fail(e);
}
Statement statement = methodInvoker(frameworkMethod, testInstance);
statement = possiblyExpectingExceptions(frameworkMethod, testInstance, statement);
statement = withBefores(frameworkMethod, testInstance, statement);
statement = withAfters(frameworkMethod, testInstance, statement);
statement = withPotentialTimeout(frameworkMethod, testInstance, statement);
statement = withPotentialRepeat(frameworkMethod, testInstance, statement);
return statement;
}
/**
* Creates a new {@link TestContextManager}. Can be overridden by
* subclasses.
* Returns <code>true</code> if {@link Ignore &#064;Ignore} is present for
* the supplied {@link FrameworkMethod test method} or if the test method is
* disabled via <code>&#064;IfProfileValue</code>.
*
* @param clazz the Class object corresponding to the test class to be
* managed
* @see ProfileValueUtils#isTestEnabledInThisEnvironment(Method, Class)
*/
protected TestContextManager createTestContextManager(Class<?> clazz) {
return new TestContextManager(clazz);
protected boolean isTestMethodIgnored(FrameworkMethod frameworkMethod) {
Method method = frameworkMethod.getMethod();
return (method.isAnnotationPresent(Ignore.class) || !ProfileValueUtils.isTestEnabledInThisEnvironment(method,
getTestClass().getJavaClass()));
}
/**
* Get the {@link TestContextManager} associated with this runner.
* Performs the same logic as
* {@link BlockJUnit4ClassRunner#possiblyExpectingExceptions(FrameworkMethod, Object, Statement)}
* except that the <em>expected exception</em> is retrieved using
* {@link #getExpectedException(FrameworkMethod)}.
*/
protected final TestContextManager getTestContextManager() {
return this.testContextManager;
@Override
protected Statement possiblyExpectingExceptions(FrameworkMethod frameworkMethod, Object testInstance, Statement next) {
Class<? extends Throwable> expectedException = getExpectedException(frameworkMethod);
return expectedException != null ? new ExpectException(next, expectedException) : next;
}
/**
* Invokes the supplied {@link Method test method} and notifies the supplied
* {@link RunNotifier} of the appropriate events.
* Get the <code>exception</code> that the supplied {@link FrameworkMethod
* test method} is expected to throw.
* <p>
* Supports both Spring's {@link ExpectedException @ExpectedException(...)}
* and JUnit's {@link Test#expected() @Test(expected=...)} annotations, but
* not both simultaneously.
* </p>
*
* @see #createTest()
* @see JUnit4ClassRunner#invokeTestMethod(Method,RunNotifier)
* @return the expected exception, or <code>null</code> if none was
* specified
*/
@Override
protected void invokeTestMethod(Method method, RunNotifier notifier) {
if (logger.isDebugEnabled()) {
logger.debug("Invoking test method [" + method.toGenericString() + "]");
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);
}
// The following is a 1-to-1 copy of the original JUnit 4.4 code, except
// that we use custom implementations for TestMethod and MethodRoadie.
return springExpectedException != null ? springExpectedException : junitExpectedException;
}
Description description = methodDescription(method);
Object testInstance;
try {
testInstance = createTest();
/**
* Supports both Spring's {@link Timed &#064;Timed} and JUnit's
* {@link Test#timeout() &#064;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);
}
catch (InvocationTargetException ex) {
notifier.fireTestFailure(new Failure(description, ex.getCause()));
return;
else if (springTimeout > 0) {
statement = new SpringFailOnTimeout(next, springTimeout);
}
catch (Exception ex) {
notifier.fireTestFailure(new Failure(description, ex));
return;
else if (junitTimeout > 0) {
statement = new FailOnTimeout(next, junitTimeout);
}
else {
statement = next;
}
SpringTestMethod testMethod = new SpringTestMethod(method, getTestClass());
new SpringMethodRoadie(getTestContextManager(), testInstance, testMethod, notifier, description).run();
return statement;
}
/**
* Retrieves the configured JUnit <code>timeout</code> from the {@link Test
* &#064;Test} annotation on the supplied {@link FrameworkMethod test
* method}.
*
* @return the timeout, or <code>0</code> 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</code> from the
* {@link Timed &#064;Timed} annotation on the supplied
* {@link FrameworkMethod test method}.
*
* @return the timeout, or <code>0</code> 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 RunSpringTestContextBefores} statement, thus preserving the
* default functionality but adding support for the Spring TestContext
* Framework.
*
* @see RunSpringTestContextBefores
*/
@Override
protected Statement withBefores(FrameworkMethod frameworkMethod, Object testInstance, Statement statement) {
Statement junitBefores = super.withBefores(frameworkMethod, testInstance, statement);
return new RunSpringTestContextBefores(junitBefores, testInstance, frameworkMethod.getMethod(),
getTestContextManager());
}
/**
* Wraps the {@link Statement} returned by the parent implementation with a
* {@link RunSpringTestContextAfters} statement, thus preserving the default
* functionality but adding support for the Spring TestContext Framework.
*
* @see RunSpringTestContextAfters
*/
@Override
protected Statement withAfters(FrameworkMethod frameworkMethod, Object testInstance, Statement statement) {
Statement junitAfters = super.withAfters(frameworkMethod, testInstance, statement);
return new RunSpringTestContextAfters(junitAfters, testInstance, frameworkMethod.getMethod(),
getTestContextManager());
}
/**
* Supports Spring's {@link Repeat &#064;Repeat} annotation by returning a
* {@link SpringRepeat} statement initialized with the configured repeat
* count or <code>1</code> 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);
}
}

354
org.springframework.test/src/main/java/org/springframework/test/context/junit4/SpringMethodRoadie.java

@ -1,354 +0,0 @@ @@ -1,354 +0,0 @@
/*
* Copyright 2002-2008 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.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.internal.AssumptionViolatedException;
import org.junit.runner.Description;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunNotifier;
import org.springframework.test.annotation.Repeat;
import org.springframework.test.annotation.Timed;
import org.springframework.test.context.TestContextManager;
/**
* <p>
* <code>SpringMethodRoadie</code> is a custom implementation of JUnit 4.4's
* {@link org.junit.internal.runners.MethodRoadie MethodRoadie}, which provides
* the following enhancements:
* </p>
* <ul>
* <li>Notifies a {@link TestContextManager} of
* {@link TestContextManager#beforeTestMethod(Object,Method) before} and
* {@link TestContextManager#afterTestMethod(Object,Method,Throwable) after}
* events.</li>
* <li>Uses a {@link SpringTestMethod} instead of JUnit 4.4's
* {@link org.junit.internal.runners.TestMethod TestMethod}.</li>
* <li>Tracks the exception thrown during execution of the test method.</li>
* </ul>
* <p>
* Due to method and field visibility constraints, the code of
* <code>MethodRoadie</code> has been duplicated here instead of subclassing
* <code>MethodRoadie</code> directly.
* </p>
*
* @author Sam Brannen
* @author Juergen Hoeller
* @since 2.5
*/
class SpringMethodRoadie {
protected static final Log logger = LogFactory.getLog(SpringMethodRoadie.class);
private final TestContextManager testContextManager;
private final Object testInstance;
private final SpringTestMethod testMethod;
private final RunNotifier notifier;
private final Description description;
private Throwable testException;
/**
* Constructs a new <code>SpringMethodRoadie</code>.
* @param testContextManager the TestContextManager to notify
* @param testInstance the test instance upon which to invoke the test method
* @param testMethod the test method to invoke
* @param notifier the RunNotifier to notify
* @param description the test description
*/
public SpringMethodRoadie(TestContextManager testContextManager, Object testInstance,
SpringTestMethod testMethod, RunNotifier notifier, Description description) {
this.testContextManager = testContextManager;
this.testInstance = testInstance;
this.testMethod = testMethod;
this.notifier = notifier;
this.description = description;
}
/**
* Runs the <em>test</em>, including notification of events to the
* {@link RunNotifier} and {@link TestContextManager} as well as proper
* handling of {@link org.junit.Ignore @Ignore},
* {@link org.junit.Test#expected() expected exceptions},
* {@link org.junit.Test#timeout() test timeouts}, and
* {@link org.junit.Assume.AssumptionViolatedException assumptions}.
*/
public void run() {
if (this.testMethod.isIgnored()) {
this.notifier.fireTestIgnored(this.description);
return;
}
this.notifier.fireTestStarted(this.description);
try {
Timed timedAnnotation = this.testMethod.getMethod().getAnnotation(Timed.class);
long springTimeout = (timedAnnotation != null && timedAnnotation.millis() > 0 ?
timedAnnotation.millis() : 0);
long junitTimeout = this.testMethod.getTimeout();
if (springTimeout > 0 && junitTimeout > 0) {
throw new IllegalStateException("Test method [" + this.testMethod.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.");
}
else if (springTimeout > 0) {
long startTime = System.currentTimeMillis();
try {
runTest();
}
finally {
long elapsed = System.currentTimeMillis() - startTime;
if (elapsed > springTimeout) {
addFailure(new TimeoutException("Took " + elapsed + " ms; limit was " + springTimeout));
}
}
}
else if (junitTimeout > 0) {
runWithTimeout(junitTimeout);
}
else {
runTest();
}
}
finally {
this.notifier.fireTestFinished(this.description);
}
}
/**
* Runs the test method on the test instance with the specified
* <code>timeout</code>.
* @param timeout the timeout in milliseconds
* @see #runWithRepetitions(Runnable)
* @see #runTestMethod()
*/
protected void runWithTimeout(final long timeout) throws CancellationException {
runWithRepetitions(new Runnable() {
public void run() {
ExecutorService service = Executors.newSingleThreadExecutor();
Future result = service.submit(new RunBeforesThenTestThenAfters());
service.shutdown();
try {
boolean terminated = service.awaitTermination(timeout, TimeUnit.MILLISECONDS);
if (!terminated) {
service.shutdownNow();
}
// Throws the exception if one occurred during the invocation.
result.get(0, TimeUnit.MILLISECONDS);
}
catch (TimeoutException ex) {
String message = "Test timed out after " + timeout + " milliseconds";
addFailure(new TimeoutException(message));
// We're cancelling repetitions here since we don't want
// the abandoned test method execution to conflict with
// further execution attempts of the same test method.
throw new CancellationException(message);
}
catch (ExecutionException ex) {
addFailure(ex.getCause());
}
catch (Exception ex) {
addFailure(ex);
}
}
});
}
/**
* Runs the test, including {@link #runBefores() @Before} and
* {@link #runAfters() @After} methods.
* @see #runWithRepetitions(Runnable)
* @see #runTestMethod()
*/
protected void runTest() {
runWithRepetitions(new RunBeforesThenTestThenAfters());
}
/**
* Runs the supplied <code>test</code> with repetitions. Checks for the
* presence of {@link Repeat @Repeat} to determine if the test should be run
* more than once. The test will be run at least once.
* @param test the runnable test
* @see Repeat
*/
protected void runWithRepetitions(Runnable test) {
Method method = this.testMethod.getMethod();
Repeat repeat = method.getAnnotation(Repeat.class);
int runs = (repeat != null && repeat.value() > 1 ? repeat.value() : 1);
for (int i = 0; i < runs; i++) {
if (runs > 1 && logger.isInfoEnabled()) {
logger.info("Repetition " + (i + 1) + " of test " + method.getName());
}
try {
test.run();
}
catch (CancellationException ex) {
break;
}
}
}
/**
* Runs the test method on the test instance, processing exceptions
* (both expected and unexpected), assumptions, and registering
* failures as necessary.
*/
protected void runTestMethod() {
this.testException = null;
try {
this.testMethod.invoke(this.testInstance);
if (this.testMethod.expectsException()) {
addFailure(new AssertionError("Expected exception: " + this.testMethod.getExpectedException().getName()));
}
}
catch (InvocationTargetException ex) {
this.testException = ex.getTargetException();
if (!(this.testException instanceof AssumptionViolatedException)) {
if (!this.testMethod.expectsException()) {
addFailure(this.testException);
}
else if (this.testMethod.isUnexpected(this.testException)) {
addFailure(new Exception("Unexpected exception, expected <" +
this.testMethod.getExpectedException().getName() + "> but was <" +
this.testException.getClass().getName() + ">", this.testException));
}
}
}
catch (Throwable ex) {
addFailure(ex);
}
finally {
if (logger.isDebugEnabled()) {
logger.debug("Test method [" + this.testMethod.getMethod() + "] threw exception: " +
this.testException);
}
}
}
/**
* Calls {@link TestContextManager#beforeTestMethod} and then runs
* {@link org.junit.Before @Before methods}, registering failures
* and throwing {@link FailedBefore} exceptions as necessary.
* @throws FailedBefore if an error occurs while executing a <em>before</em> method
*/
protected void runBefores() throws FailedBefore {
try {
this.testContextManager.beforeTestMethod(this.testInstance, this.testMethod.getMethod());
List<Method> befores = this.testMethod.getBefores();
for (Method before : befores) {
before.invoke(this.testInstance);
}
}
catch (InvocationTargetException ex) {
Throwable targetEx = ex.getTargetException();
if (!(targetEx instanceof AssumptionViolatedException)) {
addFailure(targetEx);
}
throw new FailedBefore();
}
catch (Throwable ex) {
addFailure(ex);
throw new FailedBefore();
}
}
/**
* Runs {@link org.junit.After @After methods}, registering failures as
* necessary, and then calls {@link TestContextManager#afterTestMethod}.
*/
protected void runAfters() {
List<Method> afters = this.testMethod.getAfters();
for (Method after : afters) {
try {
after.invoke(this.testInstance);
}
catch (InvocationTargetException ex) {
addFailure(ex.getTargetException());
}
catch (Throwable ex) {
addFailure(ex);
}
}
try {
this.testContextManager.afterTestMethod(this.testInstance, this.testMethod.getMethod(), this.testException);
}
catch (Throwable ex) {
addFailure(ex);
}
}
/**
* Fire a failure for the supplied <code>exception</code> with the
* {@link RunNotifier}.
* @param exception the exception upon which to base the failure
*/
protected void addFailure(Throwable exception) {
this.notifier.fireTestFailure(new Failure(this.description, exception));
}
/**
* Runs the test method, executing <code>@Before</code> and <code>@After</code>
* methods accordingly.
*/
private class RunBeforesThenTestThenAfters implements Runnable {
public void run() {
try {
runBefores();
runTestMethod();
}
catch (FailedBefore ex) {
// ignore
}
finally {
runAfters();
}
}
}
/**
* Marker exception to signal that an exception was encountered while
* executing an {@link org.junit.Before @Before} method.
*/
private static class FailedBefore extends Exception {
}
}

183
org.springframework.test/src/main/java/org/springframework/test/context/junit4/SpringTestMethod.java

@ -1,183 +0,0 @@ @@ -1,183 +0,0 @@
/*
* Copyright 2002-2008 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.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.Test.None;
import org.junit.internal.runners.TestClass;
import org.springframework.test.annotation.ExpectedException;
import org.springframework.test.annotation.ProfileValueSource;
import org.springframework.test.annotation.ProfileValueUtils;
/**
* SpringTestMethod is a custom implementation of JUnit 4.4's
* {@link org.junit.internal.runners.TestMethod TestMethod}. Due to method and
* field visibility constraints, the code of TestMethod has been duplicated here
* instead of subclassing TestMethod directly.
*
* <p>SpringTestMethod also provides support for
* {@link org.springframework.test.annotation.IfProfileValue @IfProfileValue}
* and {@link ExpectedException @ExpectedException}. See {@link #isIgnored()}
* and {@link #getExpectedException()} for further details.
*
* @author Sam Brannen
* @since 2.5
*/
class SpringTestMethod {
private static final Log logger = LogFactory.getLog(SpringTestMethod.class);
private final Method method;
private final TestClass testClass;
/**
* Constructs a test method for the supplied {@link Method method} and
* {@link TestClass test class}; and retrieves the configured (or default)
* {@link ProfileValueSource}.
* @param method The test method
* @param testClass the test class
*/
public SpringTestMethod(Method method, TestClass testClass) {
this.method = method;
this.testClass = testClass;
}
/**
* Determine if this test method is {@link Test#expected() expected} to
* throw an exception.
*/
public boolean expectsException() {
return (getExpectedException() != null);
}
/**
* Get the {@link After @After} methods for this test method.
*/
public List<Method> getAfters() {
return getTestClass().getAnnotatedMethods(After.class);
}
/**
* Get the {@link Before @Before} methods for this test method.
*/
public List<Method> getBefores() {
return getTestClass().getAnnotatedMethods(Before.class);
}
/**
* Get the <code>exception</code> that this test method is expected to throw.
* <p>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</code> if none was specified
*/
public Class<? extends Throwable> getExpectedException() throws IllegalStateException {
ExpectedException expectedExAnn = getMethod().getAnnotation(ExpectedException.class);
Test testAnnotation = getMethod().getAnnotation(Test.class);
Class<? extends Throwable> expectedException = null;
Class<? extends Throwable> springExpectedException =
(expectedExAnn != null && expectedExAnn.value() != null ? expectedExAnn.value() : null);
Class<? extends Throwable> junitExpectedException =
(testAnnotation != null && testAnnotation.expected() != None.class ? testAnnotation.expected() : null);
if (springExpectedException != null && junitExpectedException != null) {
String msg = "Test method [" + 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);
}
else if (springExpectedException != null) {
expectedException = springExpectedException;
}
else if (junitExpectedException != null) {
expectedException = junitExpectedException;
}
return expectedException;
}
/**
* Get the actual {@link Method method} referenced by this test method.
*/
public final Method getMethod() {
return this.method;
}
/**
* Get the {@link TestClass test class} for this test method.
*/
public final TestClass getTestClass() {
return this.testClass;
}
/**
* Get the configured <code>timeout</code> for this test method.
* <p>Supports JUnit's {@link Test#timeout() @Test(timeout=...)} annotation.
* @return the timeout, or <code>0</code> if none was specified
*/
public long getTimeout() {
Test testAnnotation = getMethod().getAnnotation(Test.class);
return (testAnnotation != null && testAnnotation.timeout() > 0 ? testAnnotation.timeout() : 0);
}
/**
* Convenience method for {@link Method#invoke(Object,Object...) invoking}
* the method associated with this test method. Throws exceptions consistent
* with {@link Method#invoke(Object,Object...) Method.invoke()}.
* @param testInstance the test instance upon which to invoke the method
*/
public void invoke(Object testInstance) throws IllegalAccessException, InvocationTargetException {
getMethod().invoke(testInstance);
}
/**
* Determine if this test method should be ignored.
* @return <code>true</code> if this test method should be ignored
* @see ProfileValueUtils#isTestEnabledInThisEnvironment
*/
public boolean isIgnored() {
return (getMethod().isAnnotationPresent(Ignore.class) ||
!ProfileValueUtils.isTestEnabledInThisEnvironment(this.method, this.testClass.getJavaClass()));
}
/**
* Determine if this test method {@link Test#expected() expects} exceptions
* of the type of the supplied <code>exception</code> to be thrown.
* @param exception the thrown exception
* @return <code>true</code> if the supplied exception was of an expected type
*/
public boolean isUnexpected(Throwable exception) {
return !getExpectedException().isAssignableFrom(exception.getClass());
}
}

2
org.springframework.test/src/main/java/org/springframework/test/context/junit4/package.html

@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
<body>
<p>Support classes for ApplicationContext-based and transactional
tests run with JUnit 4.4 and the <em>Spring TestContext Framework</em>.</p>
tests run with JUnit 4.5 and the <em>Spring TestContext Framework</em>.</p>
</body>
</html>

Loading…
Cancel
Save