• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hazendaz / jmockit1 / 551

29 Nov 2025 11:34PM UTC coverage: 72.096% (-0.1%) from 72.2%
551

push

github

web-flow
Merge pull request #424 from hazendaz/expectedExceptionAnnotation

Introduction of 'ExpectedException' annotation

5685 of 8390 branches covered (67.76%)

Branch coverage included in aggregate %.

22 of 30 new or added lines in 1 file covered. (73.33%)

14 existing lines in 3 files now uncovered.

11933 of 16047 relevant lines covered (74.36%)

0.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

59.75
/main/src/main/java/mockit/integration/junit5/JMockitExtension.java
1
/*
2
 * MIT License
3
 * Copyright (c) 2006-2025 JMockit developers
4
 * See LICENSE file for full license text.
5
 */
6
package mockit.integration.junit5;
7

8
import edu.umd.cs.findbugs.annotations.NonNull;
9
import edu.umd.cs.findbugs.annotations.Nullable;
10

11
import java.lang.reflect.Method;
12
import java.util.Arrays;
13
import java.util.stream.Collectors;
14

15
import mockit.Capturing;
16
import mockit.Injectable;
17
import mockit.Mocked;
18
import mockit.Tested;
19
import mockit.integration.TestRunnerDecorator;
20
import mockit.internal.expectations.RecordAndReplayExecution;
21
import mockit.internal.state.SavePoint;
22
import mockit.internal.state.TestRun;
23
import mockit.internal.util.StackTrace;
24
import mockit.internal.util.Utilities;
25

26
import org.junit.jupiter.api.BeforeAll;
27
import org.junit.jupiter.api.BeforeEach;
28
import org.junit.jupiter.api.Nested;
29
import org.junit.jupiter.api.extension.AfterAllCallback;
30
import org.junit.jupiter.api.extension.AfterEachCallback;
31
import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
32
import org.junit.jupiter.api.extension.BeforeAllCallback;
33
import org.junit.jupiter.api.extension.BeforeEachCallback;
34
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
35
import org.junit.jupiter.api.extension.ExtensionContext;
36
import org.junit.jupiter.api.extension.ParameterContext;
37
import org.junit.jupiter.api.extension.ParameterResolver;
38
import org.junit.jupiter.api.extension.TestExecutionExceptionHandler;
39
import org.junit.jupiter.api.extension.TestInstancePostProcessor;
40
import org.opentest4j.TestAbortedException;
41

42
public final class JMockitExtension extends TestRunnerDecorator implements BeforeAllCallback, AfterAllCallback,
1✔
43
        TestInstancePostProcessor, BeforeEachCallback, AfterEachCallback, BeforeTestExecutionCallback,
44
        AfterTestExecutionCallback, ParameterResolver, TestExecutionExceptionHandler {
45
    @Nullable
46
    private SavePoint savePointForTestClass;
47
    @Nullable
48
    private SavePoint savePointForTest;
49
    @Nullable
50
    private SavePoint savePointForTestMethod;
51
    @Nullable
52
    private Throwable thrownByTest;
53
    private Object[] parameterValues;
54
    private ParamValueInitContext initContext = new ParamValueInitContext(null, null, null,
1✔
55
            "No callbacks have been processed, preventing parameter population");
56

57
    @Override
58
    public void beforeAll(@NonNull ExtensionContext context) {
59
        if (!isRegularTestClass(context)) {
1!
60
            return;
×
61
        }
62

63
        @Nullable
64
        Class<?> testClass = context.getTestClass().orElse(null);
1✔
65
        savePointForTestClass = new SavePoint();
1✔
66
        // Ensure JMockit state and test class logic is handled before any test instance is created
67
        if (testClass != null) {
1!
68
            updateTestClassState(null, testClass);
1✔
69
        }
70

71
        if (testClass == null) {
1!
72
            initContext = new ParamValueInitContext(null, null, null,
×
73
                    "@BeforeAll setup failed to acquire 'Class' of test");
74
            return;
×
75
        }
76

77
        // @BeforeAll can be used on instance methods depending on @TestInstance(PER_CLASS) usage
78
        Object testInstance = context.getTestInstance().orElse(null);
1✔
79
        Method beforeAllMethod = Utilities.getAnnotatedDeclaredMethod(testClass, BeforeAll.class);
1✔
80
        if (testInstance == null) {
1!
81
            initContext = new ParamValueInitContext(null, testClass, beforeAllMethod,
1✔
82
                    "@BeforeAll setup failed to acquire instance of test class");
83
            return;
1✔
84
        }
85

86
        if (beforeAllMethod != null) {
×
87
            initContext = new ParamValueInitContext(testInstance, testClass, beforeAllMethod, null);
×
88
            parameterValues = createInstancesForAnnotatedParameters(testInstance, beforeAllMethod, null);
×
89
        }
90
    }
×
91

92
    private static boolean isRegularTestClass(@NonNull ExtensionContext context) {
93
        Class<?> testClass = context.getTestClass().orElse(null);
1✔
94
        return testClass != null && !testClass.isAnnotationPresent(Nested.class);
1!
95
    }
96

97
    @Override
98
    public void postProcessTestInstance(@NonNull Object testInstance, @NonNull ExtensionContext context) {
99
        if (!isRegularTestClass(context)) {
1!
100
            return;
×
101
        }
102

103
        TestRun.enterNoMockingZone();
1✔
104

105
        try {
106
            handleMockFieldsForWholeTestClass(testInstance);
1✔
107
        } finally {
108
            TestRun.exitNoMockingZone();
1✔
109
        }
110

111
        TestRun.setRunningIndividualTest(testInstance);
1✔
112
    }
1✔
113

114
    @Override
115
    public void beforeEach(@NonNull ExtensionContext context) {
116
        Object testInstance = context.getTestInstance().orElse(null);
1✔
117
        Class<?> testClass = context.getTestClass().orElse(null);
1✔
118
        if (testInstance == null) {
1!
119
            initContext = new ParamValueInitContext(null, null, null,
×
120
                    "@BeforeEach setup failed to acquire instance of test class");
121
            return;
×
122
        }
123

124
        TestRun.prepareForNextTest();
1✔
125
        TestRun.enterNoMockingZone();
1✔
126

127
        try {
128
            savePointForTest = new SavePoint();
1✔
129
            createInstancesForTestedFieldsBeforeSetup(testInstance);
1✔
130

131
            if (testClass == null) {
1!
132
                initContext = new ParamValueInitContext(null, null, null,
×
133
                        "@BeforeEach setup failed to acquire Class<?> of test");
134
                return;
×
135
            }
136

137
            Method beforeEachMethod = Utilities.getAnnotatedDeclaredMethod(testClass, BeforeEach.class);
1✔
138
            if (beforeEachMethod != null) {
1✔
139
                initContext = new ParamValueInitContext(testInstance, testClass, beforeEachMethod, null);
1✔
140
                parameterValues = createInstancesForAnnotatedParameters(testInstance, beforeEachMethod, null);
1✔
141
            }
142
        } finally {
143
            TestRun.exitNoMockingZone();
1✔
144
        }
145
    }
1✔
146

147
    @Override
148
    public void beforeTestExecution(@NonNull ExtensionContext context) {
149
        Class<?> testClass = context.getTestClass().orElse(null);
1✔
150
        Method testMethod = context.getTestMethod().orElse(null);
1✔
151
        Object testInstance = context.getTestInstance().orElse(null);
1✔
152

153
        if (testMethod == null || testInstance == null) {
1!
154
            initContext = new ParamValueInitContext(testInstance, testClass, testMethod,
×
155
                    "@Test failed to acquire instance of test class, or target method");
156
            return;
×
157
        }
158

159
        TestRun.enterNoMockingZone();
1✔
160

161
        try {
162
            savePointForTestMethod = new SavePoint();
1✔
163
            createInstancesForTestedFieldsFromBaseClasses(testInstance);
1✔
164
            initContext = new ParamValueInitContext(testInstance, testClass, testMethod, null);
1✔
165
            parameterValues = createInstancesForAnnotatedParameters(testInstance, testMethod, null);
1✔
166
            createInstancesForTestedFields(testInstance);
1✔
167
        } catch (Throwable e) {
1✔
168
            if (isExpectedException(context, e)) {
1!
169
                throw new TestAbortedException("Expected exception occurred in setup: " + e.getMessage());
1✔
170
            }
NEW
171
            throw e;
×
172
        } finally {
173
            TestRun.exitNoMockingZone();
1✔
174
        }
175

176
        TestRun.setRunningIndividualTest(testInstance);
1✔
177
    }
1✔
178

179
    @Override
180
    public boolean supportsParameter(@NonNull ParameterContext parameterContext,
181
            @NonNull ExtensionContext extensionContext) {
182
        return parameterContext.isAnnotated(Tested.class) || parameterContext.isAnnotated(Mocked.class)
1✔
183
                || parameterContext.isAnnotated(Injectable.class) || parameterContext.isAnnotated(Capturing.class);
1!
184
    }
185

186
    @Override
187
    public Object resolveParameter(@NonNull ParameterContext parameterContext,
188
            @NonNull ExtensionContext extensionContext) {
189
        int parameterIndex = parameterContext.getIndex();
1✔
190
        if (parameterValues == null) {
1!
191
            String warning = initContext.warning;
×
192
            StringBuilder exceptionMessage = new StringBuilder(
×
193
                    "JMockit failed to provide parameters to JUnit 5 ParameterResolver.");
194
            if (warning != null) {
×
195
                exceptionMessage.append("\nAdditional info: ").append(warning);
×
196
            }
197
            exceptionMessage.append("\n - Class: ").append(initContext.displayClass());
×
198
            exceptionMessage.append("\n - Method: ").append(initContext.displayMethod());
×
199
            throw new IllegalStateException(exceptionMessage.toString());
×
200
        }
201
        return parameterValues[parameterIndex];
1✔
202
    }
203

204
    @Override
205
    public void handleTestExecutionException(@NonNull ExtensionContext context, @NonNull Throwable throwable)
206
            throws Throwable {
207
        if (isExpectedException(context, throwable)) {
1!
208
            // Expected exception was thrown, suppress it (test passes)
209
            return;
1✔
210
        }
211

212
        thrownByTest = throwable;
×
213
        throw throwable;
×
214
    }
215

216
    @Override
217
    public void afterTestExecution(@NonNull ExtensionContext context) {
218
        if (savePointForTestMethod == null) {
1!
219
            return;
×
220
        }
221

222
        TestRun.enterNoMockingZone();
1✔
223

224
        try {
225
            savePointForTestMethod.rollback();
1✔
226
            savePointForTestMethod = null;
1✔
227

228
            if (thrownByTest != null) {
1!
NEW
229
                StackTrace.filterStackTrace(thrownByTest);
×
230
            }
231

232
            Error expectationsFailure = RecordAndReplayExecution.endCurrentReplayIfAny();
1✔
233
            clearTestedObjectsIfAny();
1✔
234

235
            if (expectationsFailure != null && isExpectedException(context, expectationsFailure)) {
1!
236
                // Expected JMockit error was thrown, suppress it (test passes)
237
                return;
1✔
238
            }
239

240
            if (expectationsFailure != null) {
1!
NEW
241
                StackTrace.filterStackTrace(expectationsFailure);
×
242
                throw expectationsFailure;
×
243
            }
244
        } finally {
245
            TestRun.finishCurrentTestExecution();
1✔
246
            TestRun.exitNoMockingZone();
1✔
247
        }
248
    }
1✔
249

250
    @Override
251
    public void afterEach(@NonNull ExtensionContext context) {
252
        if (savePointForTest != null) {
1!
253
            savePointForTest.rollback();
1✔
254
            savePointForTest = null;
1✔
255
        }
256
    }
1✔
257

258
    @Override
259
    public void afterAll(@NonNull ExtensionContext context) {
260
        if (savePointForTestClass != null && isRegularTestClass(context)) {
1!
261
            savePointForTestClass.rollback();
1✔
262
            savePointForTestClass = null;
1✔
263

264
            clearFieldTypeRedefinitions();
1✔
265
            TestRun.setCurrentTestClass(null);
1✔
266
        }
267
    }
1✔
268

269
    private static class ParamValueInitContext {
270
        private final Object instance;
271
        private final Class<?> clazz;
272
        private final Method method;
273
        private final String warning;
274

275
        ParamValueInitContext(Object instance, Class<?> clazz, Method method, String warning) {
1✔
276
            this.instance = instance;
1✔
277
            this.clazz = clazz;
1✔
278
            this.method = method;
1✔
279
            this.warning = warning;
1✔
280
        }
1✔
281

282
        boolean isBeforeAllMethod() {
283
            return method != null && method.getDeclaredAnnotation(BeforeAll.class) != null;
×
284
        }
285

286
        boolean isBeforeEachMethod() {
287
            return method != null && method.getDeclaredAnnotation(BeforeEach.class) != null;
×
288
        }
289

290
        String displayClass() {
291
            return clazz == null ? "<no class reference>" : clazz.getName();
×
292
        }
293

294
        String displayMethod() {
295
            if (method == null) {
×
296
                return "<no method reference>";
×
297
            }
298
            String methodPrefix = isBeforeAllMethod() ? "@BeforeAll " : isBeforeEachMethod() ? "@BeforeEach " : "";
×
299
            String args = Arrays.stream(method.getParameterTypes()).map(Class::getName)
×
300
                    .collect(Collectors.joining(", "));
×
301
            return methodPrefix + method.getName() + "(" + args + ")";
×
302
        }
303

304
        @Override
305
        public String toString() {
306
            return "ParamContext{hasInstance=" + (instance == null ? "false" : "true") + ", class=" + clazz
×
307
                    + ", method=" + method + ", warning=" + warning + "}";
308
        }
309
    }
310

311
    private static boolean isExpectedException(@NonNull ExtensionContext context, @NonNull Throwable throwable) {
312
        Method testMethod = context.getTestMethod().orElse(null);
1✔
313
        ExpectedException expectedException = testMethod != null ? testMethod.getAnnotation(ExpectedException.class)
1!
314
                : null;
1✔
315

316
        if (expectedException == null) {
1!
NEW
317
            return false;
×
318
        }
319

320
        return expectedException.value().isInstance(throwable) && matchesExpectedMessages(throwable, expectedException);
1!
321
    }
322

323
    private static boolean matchesExpectedMessages(Throwable throwable, ExpectedException expectedException) {
324
        String[] expectedMessages = expectedException.expectedMessages();
1✔
325
        if (expectedMessages.length == 0) {
1✔
326
            // No message requirement
327
            return true;
1✔
328
        }
329

330
        String actualMessage = throwable.getMessage();
1✔
331
        if (actualMessage == null) {
1!
NEW
332
            return false;
×
333
        }
334

335
        boolean contains = expectedException.messageContains();
1✔
336
        for (String expected : expectedMessages) {
1!
337
            if (contains) {
1!
338
                if (actualMessage.contains(expected)) {
1!
339
                    return true;
1✔
340
                }
NEW
341
            } else if (actualMessage.equals(expected)) {
×
NEW
342
                return true;
×
343
            }
344
        }
NEW
345
        return false;
×
346
    }
347

348
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc