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.junit4;
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.integration.TestRunnerDecorator;
16  import mockit.internal.expectations.RecordAndReplayExecution;
17  import mockit.internal.faking.FakeInvocation;
18  import mockit.internal.state.SavePoint;
19  import mockit.internal.state.TestRun;
20  
21  import org.junit.After;
22  import org.junit.AfterClass;
23  import org.junit.Before;
24  import org.junit.BeforeClass;
25  import org.junit.Test;
26  import org.junit.runners.model.FrameworkMethod;
27  
28  final class JUnit4TestRunnerDecorator extends TestRunnerDecorator {
29      @Nullable
30      Object invokeExplosively(@NonNull FakeInvocation invocation, @Nullable Object target, Object... params)
31              throws Throwable {
32          FrameworkMethod it = invocation.getInvokedInstance();
33          assert it != null;
34  
35          // A @BeforeClass/@AfterClass method:
36          if (target == null) {
37              try {
38                  return executeClassMethod(invocation, params);
39              } catch (Throwable t) {
40                  filterStackTrace(t);
41                  throw t;
42              }
43          }
44  
45          handleMockingOutsideTestMethods(target);
46  
47          // A @Before/@After method:
48          if (it.getAnnotation(Test.class) == null) {
49              if (shouldPrepareForNextTest && it.getAnnotation(Before.class) != null) {
50                  prepareToExecuteSetupMethod(target);
51              }
52  
53              TestRun.setRunningIndividualTest(target);
54  
55              try {
56                  invocation.prepareToProceedFromNonRecursiveMock();
57                  return it.invokeExplosively(target, params);
58              } catch (Throwable t) {
59                  // noinspection ThrowableNotThrown
60                  RecordAndReplayExecution.endCurrentReplayIfAny();
61                  filterStackTrace(t);
62                  throw t;
63              } finally {
64                  if (it.getAnnotation(After.class) != null) {
65                      shouldPrepareForNextTest = true;
66                  }
67              }
68          }
69  
70          if (shouldPrepareForNextTest) {
71              prepareForNextTest();
72          }
73  
74          shouldPrepareForNextTest = true;
75  
76          try {
77              executeTestMethod(invocation, target, params);
78              return null; // it's a test method, therefore has void return type
79          } catch (Throwable t) {
80              filterStackTrace(t);
81              throw t;
82          } finally {
83              TestRun.finishCurrentTestExecution();
84          }
85      }
86  
87      @Nullable
88      private static Object executeClassMethod(@NonNull FakeInvocation inv, @NonNull Object[] params) throws Throwable {
89          FrameworkMethod method = inv.getInvokedInstance();
90          assert method != null;
91          handleMockingOutsideTests(method);
92  
93          TestRun.clearCurrentTestInstance();
94          inv.prepareToProceedFromNonRecursiveMock();
95  
96          return method.invokeExplosively(null, params);
97      }
98  
99      private void prepareToExecuteSetupMethod(@NonNull Object target) {
100         discardTestLevelMockedTypes();
101         prepareForNextTest();
102         shouldPrepareForNextTest = false;
103         createInstancesForTestedFieldsBeforeSetup(target);
104     }
105 
106     private static void handleMockingOutsideTests(@NonNull FrameworkMethod it) {
107         Class<?> testClass = it.getMethod().getDeclaringClass();
108 
109         TestRun.enterNoMockingZone();
110 
111         try {
112             Class<?> currentTestClass = TestRun.getCurrentTestClass();
113 
114             if (currentTestClass != null && testClass.isAssignableFrom(currentTestClass)
115                     && it.getAnnotation(AfterClass.class) != null) {
116                 cleanUpMocksFromPreviousTest();
117             }
118 
119             if (it.getAnnotation(BeforeClass.class) != null) {
120                 updateTestClassState(null, testClass);
121             }
122         } finally {
123             TestRun.exitNoMockingZone();
124         }
125     }
126 
127     private static void handleMockingOutsideTestMethods(@NonNull Object target) {
128         Class<?> testClass = target.getClass();
129 
130         TestRun.enterNoMockingZone();
131 
132         try {
133             updateTestClassState(target, testClass);
134         } finally {
135             TestRun.exitNoMockingZone();
136         }
137     }
138 
139     private static void executeTestMethod(@NonNull FakeInvocation invocation, @NonNull Object testInstance,
140             @Nullable Object... parameters) throws Throwable {
141         SavePoint savePoint = new SavePoint();
142 
143         TestRun.setRunningIndividualTest(testInstance);
144 
145         FrameworkMethod it = invocation.getInvokedInstance();
146         assert it != null;
147         Method testMethod = it.getMethod();
148         Throwable testFailure = null;
149         boolean testFailureExpected = false;
150 
151         try {
152             createInstancesForTestedFieldsFromBaseClasses(testInstance);
153             Object[] annotatedParameters = createInstancesForAnnotatedParameters(testInstance, testMethod, parameters);
154             createInstancesForTestedFields(testInstance);
155 
156             invocation.prepareToProceedFromNonRecursiveMock();
157 
158             Object[] params = annotatedParameters == null ? parameters : annotatedParameters;
159             it.invokeExplosively(testInstance, params);
160         } catch (Throwable thrownByTest) {
161             testFailure = thrownByTest;
162             Class<?> expectedType = testMethod.getAnnotation(Test.class).expected();
163             testFailureExpected = expectedType.isAssignableFrom(thrownByTest.getClass());
164         } finally {
165             concludeTestMethodExecution(savePoint, testFailure, testFailureExpected);
166         }
167     }
168 }