View Javadoc
1   /*
2    * Copyright (c) 2006 JMockit developers
3    * This file is subject to the terms of the MIT license (see LICENSE.txt).
4    */
5   package mockit.integration.testng;
6   
7   import static mockit.internal.util.StackTrace.filterStackTrace;
8   
9   import edu.umd.cs.findbugs.annotations.NonNull;
10  import edu.umd.cs.findbugs.annotations.Nullable;
11  
12  import java.lang.reflect.Method;
13  
14  import mockit.Expectations;
15  import mockit.coverage.testRedundancy.TestCoverage;
16  import mockit.integration.TestRunnerDecorator;
17  import mockit.internal.state.SavePoint;
18  import mockit.internal.state.TestRun;
19  
20  import org.testng.IExecutionListener;
21  import org.testng.IInvokedMethod;
22  import org.testng.IInvokedMethodListener;
23  import org.testng.ITestNGMethod;
24  import org.testng.ITestResult;
25  import org.testng.TestException;
26  import org.testng.annotations.Test;
27  
28  /**
29   * Provides callbacks to be called by the TestNG 6.2+ test runner for each test execution. JMockit will then assert any
30   * expectations recorded in {@link Expectations} subclasses during the test.
31   * <p>
32   * This class is not supposed to be accessed from user code; it will be automatically loaded at startup.
33   */
34  public final class TestNGRunnerDecorator extends TestRunnerDecorator
35          implements IInvokedMethodListener, IExecutionListener {
36      @NonNull
37      private final ThreadLocal<SavePoint> savePoint = new ThreadLocal<>();
38  
39      @Override
40      public void beforeInvocation(@NonNull IInvokedMethod invokedMethod, @NonNull ITestResult testResult) {
41          ITestNGMethod testNGMethod = testResult.getMethod();
42          Class<?> testClass = testResult.getTestClass().getRealClass();
43  
44          TestRun.clearNoMockingZone();
45  
46          if (!invokedMethod.isTestMethod()) {
47              beforeConfigurationMethod(testNGMethod, testClass);
48              return;
49          }
50  
51          Method method = testNGMethod.getConstructorOrMethod().getMethod();
52          exportCurrentTestMethodIfApplicable(method);
53  
54          Object testInstance = testResult.getInstance();
55  
56          if (testInstance == null || testInstance.getClass() != testClass) {
57              // Happens when TestNG is running a JUnit test class, for which "TestResult#getInstance()" erroneously
58              // returns a
59              // org.junit.runner.Description object.
60              return;
61          }
62  
63          TestRun.enterNoMockingZone();
64  
65          try {
66              updateTestClassState(testInstance, testClass);
67              TestRun.setRunningIndividualTest(testInstance);
68  
69              SavePoint testMethodSavePoint = new SavePoint();
70              savePoint.set(testMethodSavePoint);
71  
72              if (shouldPrepareForNextTest) {
73                  TestRun.prepareForNextTest();
74                  shouldPrepareForNextTest = false;
75                  clearTestedObjectsCreatedDuringSetup();
76              }
77  
78              createInstancesForTestedFieldsFromBaseClasses(testInstance);
79              createInstancesForTestedFields(testInstance);
80          } finally {
81              TestRun.exitNoMockingZone();
82          }
83      }
84  
85      private static void exportCurrentTestMethodIfApplicable(@Nullable Method testMethod) {
86          TestCoverage testCoverage = TestCoverage.INSTANCE;
87  
88          if (testCoverage != null) {
89              testCoverage.setCurrentTestMethod(testMethod);
90          }
91      }
92  
93      private void beforeConfigurationMethod(@NonNull ITestNGMethod method, @NonNull Class<?> testClass) {
94          TestRun.enterNoMockingZone();
95  
96          try {
97              updateTestClassState(null, testClass);
98  
99              if (method.isBeforeMethodConfiguration()) {
100                 if (shouldPrepareForNextTest) {
101                     discardTestLevelMockedTypes();
102                     clearTestedObjectsCreatedDuringSetup();
103                 }
104 
105                 Object testInstance = method.getInstance();
106                 updateTestClassState(testInstance, testClass);
107 
108                 if (shouldPrepareForNextTest) {
109                     prepareForNextTest();
110                     shouldPrepareForNextTest = false;
111                     createInstancesForTestedFieldsBeforeSetup(testInstance);
112                 }
113 
114                 TestRun.setRunningIndividualTest(testInstance);
115             } else if (method.isAfterClassConfiguration()) {
116                 TestRun.getExecutingTest().setRecordAndReplay(null);
117                 cleanUpMocksFromPreviousTest();
118                 TestRun.clearCurrentTestInstance();
119             } else if (!method.isAfterMethodConfiguration() && !method.isBeforeClassConfiguration()) {
120                 TestRun.getExecutingTest().setRecordAndReplay(null);
121                 cleanUpMocksFromPreviousTestClass();
122                 TestRun.clearCurrentTestInstance();
123                 TestRun.setCurrentTestClass(null);
124             }
125         } finally {
126             TestRun.exitNoMockingZone();
127         }
128     }
129 
130     @Override
131     public void afterInvocation(@NonNull IInvokedMethod invokedMethod, @NonNull ITestResult testResult) {
132         if (!invokedMethod.isTestMethod()) {
133             afterConfigurationMethod(testResult);
134             return;
135         }
136 
137         exportCurrentTestMethodIfApplicable(null);
138 
139         SavePoint testMethodSavePoint = savePoint.get();
140 
141         if (testMethodSavePoint == null) {
142             return;
143         }
144 
145         TestRun.enterNoMockingZone();
146         shouldPrepareForNextTest = true;
147         savePoint.remove();
148 
149         Throwable thrownByTest = testResult.getThrowable();
150 
151         try {
152             if (thrownByTest == null) {
153                 concludeTestExecutionWithNothingThrown(testMethodSavePoint, testResult);
154             } else if (thrownByTest instanceof TestException) {
155                 concludeTestExecutionWithExpectedExceptionNotThrown(invokedMethod, testMethodSavePoint, testResult);
156             } else if (testResult.isSuccess()) {
157                 concludeTestExecutionWithExpectedExceptionThrown(testMethodSavePoint, testResult, thrownByTest);
158             } else {
159                 concludeTestExecutionWithUnexpectedExceptionThrown(testMethodSavePoint, thrownByTest);
160             }
161         } finally {
162             TestRun.finishCurrentTestExecution();
163             TestRun.clearCurrentTestInstance();
164         }
165     }
166 
167     private static void afterConfigurationMethod(@NonNull ITestResult testResult) {
168         TestRun.enterNoMockingZone();
169 
170         try {
171             ITestNGMethod method = testResult.getMethod();
172 
173             if (method.isAfterMethodConfiguration()) {
174                 Throwable thrownAfterTest = testResult.getThrowable();
175 
176                 if (thrownAfterTest != null) {
177                     filterStackTrace(thrownAfterTest);
178                 }
179             }
180         } finally {
181             TestRun.exitNoMockingZone();
182         }
183     }
184 
185     private static void concludeTestExecutionWithNothingThrown(@NonNull SavePoint testMethodSavePoint,
186             @NonNull ITestResult testResult) {
187         try {
188             concludeTestMethodExecution(testMethodSavePoint, null, false);
189         } catch (Throwable t) {
190             filterStackTrace(t);
191             testResult.setThrowable(t);
192             testResult.setStatus(ITestResult.FAILURE);
193         }
194     }
195 
196     private static void concludeTestExecutionWithExpectedExceptionNotThrown(@NonNull IInvokedMethod invokedMethod,
197             @NonNull SavePoint testMethodSavePoint, @NonNull ITestResult testResult) {
198         try {
199             concludeTestMethodExecution(testMethodSavePoint, null, false);
200         } catch (Throwable t) {
201             filterStackTrace(t);
202 
203             if (isExpectedException(invokedMethod, t)) {
204                 testResult.setThrowable(null);
205                 testResult.setStatus(ITestResult.SUCCESS);
206             } else {
207                 filterStackTrace(testResult.getThrowable());
208             }
209         }
210     }
211 
212     private static void concludeTestExecutionWithExpectedExceptionThrown(@NonNull SavePoint testMethodSavePoint,
213             @NonNull ITestResult testResult, @NonNull Throwable thrownByTest) {
214         filterStackTrace(thrownByTest);
215 
216         try {
217             concludeTestMethodExecution(testMethodSavePoint, thrownByTest, true);
218         } catch (Throwable t) {
219             if (t != thrownByTest) {
220                 filterStackTrace(t);
221                 testResult.setThrowable(t);
222                 testResult.setStatus(ITestResult.FAILURE);
223             }
224         }
225     }
226 
227     private static void concludeTestExecutionWithUnexpectedExceptionThrown(@NonNull SavePoint testMethodSavePoint,
228             @NonNull Throwable thrownByTest) {
229         filterStackTrace(thrownByTest);
230 
231         try {
232             concludeTestMethodExecution(testMethodSavePoint, thrownByTest, false);
233         } catch (Throwable ignored) {
234         }
235     }
236 
237     private static boolean isExpectedException(@NonNull IInvokedMethod invokedMethod, @NonNull Throwable thrownByTest) {
238         Method testMethod = invokedMethod.getTestMethod().getConstructorOrMethod().getMethod();
239         Class<?>[] expectedExceptions = testMethod.getAnnotation(Test.class).expectedExceptions();
240         Class<? extends Throwable> thrownExceptionType = thrownByTest.getClass();
241 
242         for (Class<?> expectedException : expectedExceptions) {
243             if (expectedException.isAssignableFrom(thrownExceptionType)) {
244                 return true;
245             }
246         }
247 
248         return false;
249     }
250 
251     @Override
252     public void onExecutionStart() {
253     }
254 
255     @Override
256     public void onExecutionFinish() {
257         TestRun.enterNoMockingZone();
258 
259         try {
260             TestRunnerDecorator.cleanUpAllMocks();
261         } finally {
262             // Maven Surefire, somehow, runs these methods twice per test run.
263             TestRun.clearNoMockingZone();
264         }
265     }
266 }