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