org.unitils.UnitilsJUnit4TestClassRunner Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2008, Unitils.org
*
* 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.unitils;
import org.junit.internal.runners.*;
import org.junit.runner.Description;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunNotifier;
import org.unitils.core.TestListener;
import org.unitils.core.Unitils;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.junit.runner.notification.RunListener;
import org.unitils.core.UnitilsException;
/**
* Custom test runner that will Unitils-enable your test. This will make sure that the
* core unitils test listener methods are invoked in the expected order. See {@link TestListener} for
* more information on the listener invocation order.
*
* NOTE: if a test fails, the error is logged as debug logging. This is a temporary work-around for
* a problem with IntelliJ JUnit-4 runner that reports a 'Wrong test finished' error when something went wrong
* in the before. [IDEA-12498]
*
* @author Tim Ducheyne
* @author Filip Neven
*/
public class UnitilsJUnit4TestClassRunner extends JUnit4ClassRunner implements TestRunnerAccessor{
/**
* Creates a test runner that runs all test methods in the given class.
*
* @param testClass the class, not null
* @throws InitializationError
*/
public UnitilsJUnit4TestClassRunner(Class> testClass) throws InitializationError {
super(testClass);
}
@Override
public void run(final RunNotifier notifier) {
ClassRoadie classRoadie = new ClassRoadie(notifier, getTestClass(), getDescription(), new Runnable() {
public void run() {
runMethods(notifier);
}
});
Unitils.getInstance().getTestContext().setRunner(this);
try {
getTestListener().beforeTestClass(getTestClass().getJavaClass());
classRoadie.runProtected();
} catch (Throwable t) {
notifier.fireTestFailure(new Failure(getDescription(), t));
}
}
/**
* Overridden JUnit4 method to be able to create a CustomMethodRoadie that will invoke the
* unitils test listener methods at the appropriate moments.
*/
protected void invokeTestMethod(Method method, RunNotifier notifier) {
Description description = methodDescription(method);
Object testObject;
try {
testObject = createTest();
} catch (InvocationTargetException e) {
testAborted(notifier, description, e.getCause());
return;
} catch (Exception e) {
testAborted(notifier, description, e);
return;
}
TestMethod testMethod = wrapMethod(method);
if (!testMethod.isIgnored()) {
getTestListener().afterCreateTestObject(testObject);
}
if(getTestListener().shouldInvokeTestMethod(testObject, method)) {
createMethodRoadie(testObject, method, testMethod, notifier, description).run();
}
}
private void testAborted(RunNotifier notifier, Description description,
Throwable e) {
notifier.fireTestStarted(description);
notifier.fireTestFailure(new Failure(description, e));
notifier.fireTestFinished(description);
}
/**
* Returns the JUnit 4 MethodRoadie object that is used to execute the test method
*
* @param testObject The test instance, not null
* @param testMethod The test method, not null
* @param jUnitTestMethod The JUnit test method
* @param notifier The run listener, not null
* @param description A test description
* @return An JUnit MethodRoadie
*/
protected MethodRoadie createMethodRoadie(Object testObject, Method testMethod, TestMethod jUnitTestMethod, RunNotifier notifier, Description description) {
return new TestListenerInvokingMethodRoadie(testObject, testMethod, jUnitTestMethod, notifier, description);
}
/**
* This method allows access to the test invocation, from within the modules (who get a reference to the runner
* via the context). We do not check for listeners veto's (typically, these modules will veto the execution of
* the test in normal execution mode, and call this method later on to do their logic).
*
* @param testObject
* @param method
*/
public void executeTestMethod(Object testObject, Method method) {
TestMethod testMethod = wrapMethod(method);
RunNotifier notifier = new RunNotifier();
InterceptingListener listener = new InterceptingListener();
notifier.addListener(listener);
Description description = methodDescription(method);
createMethodRoadie(testObject, method, testMethod, notifier, description).run();
if(listener.isFailed()) {
StringBuilder msg = new StringBuilder("Exeception while executing ");
msg.append(method.getName()).append(": ").append(listener.getFailure().getDescription());
throw new UnitilsException(msg.toString(),listener.getFailure().getException());
}
}
/**
* Custom method roadie that invokes the unitils test listener methods at the apropriate moments.
*/
protected class TestListenerInvokingMethodRoadie extends MethodRoadie {
/* Instance under test */
protected Object testObject;
/* Method under test */
protected Method testMethod;
protected Throwable throwable;
/**
* Creates a method roadie.
*
* @param testObject The test instance, not null
* @param testMethod The test method, not null
* @param jUnitTestMethod The JUnit test method
* @param notifier The run listener, not null
* @param description A test description
*/
public TestListenerInvokingMethodRoadie(Object testObject, Method testMethod, TestMethod jUnitTestMethod, RunNotifier notifier, Description description) {
super(testObject, jUnitTestMethod, notifier, description);
this.testObject = testObject;
this.testMethod = testMethod;
}
/**
* Overriden JUnit4 method to be able to call {@link TestListener#afterTestTearDown}.
*/
@Override
public void runBeforesThenTestThenAfters(Runnable test) {
try {
getTestListener().beforeTestSetUp(testObject, testMethod);
} catch (Throwable t) {
addFailure(t);
}
if (throwable == null) {
super.runBeforesThenTestThenAfters(test);
}
try {
getTestListener().afterTestTearDown(testObject, testMethod);
} catch (Throwable t) {
addFailure(t);
}
}
@Override
protected void runTestMethod() {
try {
getTestListener().beforeTestMethod(testObject, testMethod);
} catch (Throwable t) {
addFailure(t);
}
if (throwable == null) {
super.runTestMethod();
}
try {
getTestListener().afterTestMethod(testObject, testMethod, throwable);
} catch (Throwable t) {
addFailure(t);
}
}
/**
* Registers a test failure
*
* @param t The exception, not null
*/
@Override
protected void addFailure(Throwable t) {
// first exception is typically the most meaningful, so ignore second exception
if (throwable == null) {
throwable = t;
super.addFailure(t);
}
}
}
/**
* @return The unitils test listener
*/
protected TestListener getTestListener() {
return getUnitils().getTestListener();
}
/**
* Returns the default singleton instance of Unitils
*
* @return the Unitils instance, not null
*/
protected Unitils getUnitils() {
return Unitils.getInstance();
}
}
class InterceptingListener extends RunListener {
private Failure failure = null;
@Override
public void testFailure(Failure failure) throws Exception {
this.failure = failure;
}
public boolean isFailed() {
return this.failure != null;
}
public Failure getFailure() {
return this.failure;
}
}