package org.powermock.modules.junit4.legacy.internal.impl.testcaseworkaround; import junit.framework.TestCase; import org.junit.internal.runners.BeforeAndAfterRunner; import org.junit.internal.runners.TestIntrospector; import org.junit.internal.runners.TestMethodRunner; import org.junit.runner.Description; import org.junit.runner.notification.RunNotifier; import org.powermock.reflect.Whitebox; import org.powermock.tests.utils.PowerMockTestNotifier; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * This class is needed because the test method runner creates a new instance of * a {@link TestIntrospector} in its constructor. The TestIntrospector needs to * be changed in order to support methods not annotated with Test to avoid * NPE's. * <p> * This class also executes the setUp and tearDown methods if the test case * extends TestCase. Another thing it does is to invoke all PowerMock test * listeners for events. */ public class PowerMockJUnit4LegacyTestMethodRunner extends TestMethodRunner { private final PowerMockJUnit4LegacyTestIntrospector testIntrospector; private final Method method; private final Description description; private final RunNotifier notifier; private final PowerMockTestNotifier powerMockTestNotifier; public PowerMockJUnit4LegacyTestMethodRunner(Object test, Method method, RunNotifier notifier, Description description, PowerMockTestNotifier powerMockTestNotifier) { super(test, method, notifier, description); this.method = method; this.description = description; this.notifier = notifier; this.powerMockTestNotifier = powerMockTestNotifier; testIntrospector = new PowerMockJUnit4LegacyTestIntrospector(test.getClass()); Whitebox.setInternalState(this, "fTestIntrospector", testIntrospector, TestMethodRunner.class); Whitebox.setInternalState(this, "fTestIntrospector", testIntrospector, BeforeAndAfterRunner.class); } @Override public void run() { if (testIntrospector.isIgnored(method)) { notifier.fireTestIgnored(description); return; } notifier.fireTestStarted(description); try { powerMockTestNotifier.notifyBeforeTestMethod(Whitebox.getInternalState(this, "fTest"), method, new Object[0]); long timeout = testIntrospector.getTimeout(method); // Execute the the setUp method if needed executeMethodInTestInstance("setUp"); if (timeout > 0) { // The runWithTimeout method is private in the super class, // invoke it using reflection. Whitebox.invokeMethod(this, TestMethodRunner.class, "runWithTimeout", timeout); } else { Whitebox.invokeMethod(this, TestMethodRunner.class, "runMethod"); } } catch (Exception e) { throw new RuntimeException(e); } finally { try { executeMethodInTestInstance("tearDown"); } finally { notifier.fireTestFinished(description); } } } /** * This method takes care of executing a method in the test object <i>if</i> * this object extends from {@link TestCase}. It can be used to execute the * setUp and tearDown methods for example. */ private void executeMethodInTestInstance(String methodName) { if (TestCase.class.isAssignableFrom(Whitebox.getInternalState(this, "fTest").getClass())) { Object object = Whitebox.getInternalState(this, "fTest"); try { if (object != null) { Whitebox.invokeMethod(object, methodName); } } catch (Throwable e) { if (e instanceof RuntimeException) { throw (RuntimeException) e; } throw new RuntimeException(e); } } } @Override protected void runUnprotected() { try { executeMethodBody(); if (expectsException()) addFailure(new AssertionError("Expected exception: " + expectedException().getName())); } catch (InvocationTargetException e) { Throwable actual = e.getTargetException(); if (!expectsException()) addFailure(actual); else if (isUnexpected(actual)) { String message = "Unexpected exception, expected<" + expectedException().getName() + "> but was<" + actual.getClass().getName() + ">"; addFailure(new Exception(message, actual)); } } catch (Throwable e) { addFailure(e); } } private boolean isUnexpected(Throwable exception) { return !expectedException().isAssignableFrom(exception.getClass()); } private boolean expectsException() { return expectedException() != null; } private Class<? extends Throwable> expectedException() { return testIntrospector.expectedException(method); } }