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

hazendaz / jmockit1 / 548

29 Nov 2025 11:21PM UTC coverage: 72.124% (-0.08%) from 72.2%
548

Pull #424

github

hazendaz
[junit5] Add new annotation 'ExpectedException'

This annotation enables JUnit 5 tests to specify expected JMockit exceptions (such as MissingInvocation or UnexpectedInvocation or any other) directly on the test method, mirroring the JUnit 4 @test(expected = ...) pattern or ExpectedException rule usage. It simplifies migration of JMockit-based tests from JUnit 4 to JUnit 5, ensuring consistent exception verification for mocking scenarios.

For standard exception assertions unrelated to JMockit, users should continue to use JUnit 5’s assertThrows.

In beforeTestExecution, setup issues will trigger a TestAbortedException when expected match without failing test or attempting to run internals.

handle test execution exception and after test execution will check if expected exception and suppress the exception on match.

Expected exception message can be passed following junit 4 style of contains, if exact match is needed you can disable the messageContains flag
Pull Request #424: Introduction of 'ExpectedException' annotation

5687 of 8390 branches covered (67.78%)

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.

11938 of 16047 relevant lines covered (74.39%)

0.74 hits per line

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

82.05
/main/src/main/java/mockit/integration/TestRunnerDecorator.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;
7

8
import static mockit.internal.reflection.ParameterReflection.getParameterCount;
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
import java.util.Collections;
15
import java.util.List;
16

17
import mockit.internal.expectations.RecordAndReplayExecution;
18
import mockit.internal.expectations.invocation.MissingInvocation;
19
import mockit.internal.expectations.invocation.UnexpectedInvocation;
20
import mockit.internal.expectations.mocking.FieldTypeRedefinitions;
21
import mockit.internal.expectations.mocking.ParameterTypeRedefinitions;
22
import mockit.internal.expectations.mocking.TypeRedefinitions;
23
import mockit.internal.injection.InjectionProvider;
24
import mockit.internal.injection.TestedClassInstantiations;
25
import mockit.internal.injection.TestedParameters;
26
import mockit.internal.state.SavePoint;
27
import mockit.internal.state.TestRun;
28
import mockit.internal.util.ParameterNameExtractor;
29
import mockit.internal.util.StackTrace;
30
import mockit.internal.util.TestMethod;
31

32
/**
33
 * Base class for "test runner decorators", which provide integration between JMockit and specific test runners from
34
 * JUnit and TestNG.
35
 */
36
public class TestRunnerDecorator {
37
    @Nullable
38
    private static SavePoint savePointForTestClass;
39
    @Nullable
40
    private static SavePoint savePointForTest;
41

42
    /**
43
     * A "volatile boolean" is as good as a java.util.concurrent.atomic.AtomicBoolean here, since we only need the basic
44
     * get/set operations.
45
     */
46
    protected volatile boolean shouldPrepareForNextTest;
47

48
    protected TestRunnerDecorator() {
1✔
49
        shouldPrepareForNextTest = true;
1✔
50
    }
1✔
51

52
    protected static void updateTestClassState(@Nullable Object target, @NonNull Class<?> testClass) {
53
        testClass = getActualTestClass(testClass);
1✔
54

55
        try {
56
            handleSwitchToNewTestClassIfApplicable(testClass);
1✔
57

58
            if (target != null) {
1✔
59
                handleMockFieldsForWholeTestClass(target);
1✔
60
            }
61
        } catch (Error e) {
×
62
            try {
63
                rollbackForTestClass();
×
64
            } catch (Error err) {
×
65
                StackTrace.filterStackTrace(err);
×
66
                throw err;
×
67
            }
×
68

69
            throw e;
×
70
        } catch (RuntimeException e) {
×
71
            rollbackForTestClass();
×
72
            StackTrace.filterStackTrace(e);
×
73
            throw e;
×
74
        }
1✔
75
    }
1✔
76

77
    @NonNull
78
    private static Class<?> getActualTestClass(@NonNull Class<?> testClass) {
79
        return testClass.isSynthetic() ? testClass.getSuperclass() : testClass;
1!
80
    }
81

82
    private static void handleSwitchToNewTestClassIfApplicable(@NonNull Class<?> testClass) {
83
        Class<?> currentTestClass = TestRun.getCurrentTestClass();
1✔
84

85
        if (testClass != currentTestClass) {
1✔
86
            if (currentTestClass == null) {
1✔
87
                savePointForTestClass = new SavePoint();
1✔
88
            } else if (!currentTestClass.isAssignableFrom(testClass)) {
1✔
89
                cleanUpMocksFromPreviousTestClass();
1✔
90
                savePointForTestClass = new SavePoint();
1✔
91
            }
92

93
            TestRun.setCurrentTestClass(testClass);
1✔
94
        }
95
    }
1✔
96

97
    public static void cleanUpMocksFromPreviousTestClass() {
98
        cleanUpMocks(true);
1✔
99
    }
1✔
100

101
    protected static void cleanUpMocksFromPreviousTest() {
102
        cleanUpMocks(false);
1✔
103
    }
1✔
104

105
    public static void cleanUpAllMocks() {
106
        cleanUpMocks(true);
1✔
107
        TestRun.getFakeClasses().discardStartupFakes();
1✔
108
    }
1✔
109

110
    private static void cleanUpMocks(boolean forTestClassAsWell) {
111
        discardTestLevelMockedTypes();
1✔
112

113
        if (forTestClassAsWell) {
1✔
114
            rollbackForTestClass();
1✔
115
        }
116

117
        clearFieldTypeRedefinitions();
1✔
118
    }
1✔
119

120
    private static void rollbackForTestClass() {
121
        SavePoint savePoint = savePointForTestClass;
1✔
122

123
        if (savePoint != null) {
1✔
124
            savePoint.rollback();
1✔
125
            savePointForTestClass = null;
1✔
126
        }
127
    }
1✔
128

129
    protected static void clearFieldTypeRedefinitions() {
130
        TypeRedefinitions fieldTypeRedefinitions = TestRun.getFieldTypeRedefinitions();
1✔
131

132
        if (fieldTypeRedefinitions != null) {
1✔
133
            fieldTypeRedefinitions.cleanUp();
1✔
134
            TestRun.setFieldTypeRedefinitions(null);
1✔
135
        }
136
    }
1✔
137

138
    protected static void prepareForNextTest() {
139
        if (savePointForTest == null) {
1✔
140
            savePointForTest = new SavePoint();
1✔
141
        }
142

143
        TestRun.prepareForNextTest();
1✔
144
    }
1✔
145

146
    protected static void discardTestLevelMockedTypes() {
147
        SavePoint savePoint = savePointForTest;
1✔
148

149
        if (savePoint != null) {
1✔
150
            savePoint.rollback();
1✔
151
            savePointForTest = null;
1✔
152
        }
153
    }
1✔
154

155
    protected static void handleMockFieldsForWholeTestClass(@NonNull Object target) {
156
        Class<?> testClass = getActualTestClass(target.getClass());
1✔
157
        FieldTypeRedefinitions fieldTypeRedefinitions = TestRun.getFieldTypeRedefinitions();
1✔
158

159
        if (fieldTypeRedefinitions == null) {
1✔
160
            ParameterNameExtractor.extractNames(testClass);
1✔
161

162
            fieldTypeRedefinitions = new FieldTypeRedefinitions(testClass);
1✔
163
            TestRun.setFieldTypeRedefinitions(fieldTypeRedefinitions);
1✔
164

165
            TestedClassInstantiations testedClassInstantiations = new TestedClassInstantiations();
1✔
166

167
            if (!testedClassInstantiations.findTestedAndInjectableMembers(testClass)) {
1✔
168
                testedClassInstantiations = null;
1✔
169
            }
170

171
            TestRun.setTestedClassInstantiations(testedClassInstantiations);
1✔
172
        }
173

174
        // noinspection ObjectEquality
175
        if (target != TestRun.getCurrentTestInstance()) {
1✔
176
            fieldTypeRedefinitions.assignNewInstancesToMockFields(target);
1✔
177
        }
178
    }
1✔
179

180
    protected static void createInstancesForTestedFieldsFromBaseClasses(@NonNull Object testClassInstance) {
181
        TestedClassInstantiations testedClasses = TestRun.getTestedClassInstantiations();
1✔
182

183
        if (testedClasses != null) {
1✔
184
            TestRun.enterNoMockingZone();
1✔
185

186
            try {
187
                testedClasses.assignNewInstancesToTestedFieldsFromBaseClasses(testClassInstance);
1✔
188
            } finally {
189
                TestRun.exitNoMockingZone();
1✔
190
            }
191
        }
192
    }
1✔
193

194
    protected static void createInstancesForTestedFieldsBeforeSetup(@NonNull Object testClassInstance) {
195
        TestedClassInstantiations testedClasses = TestRun.getTestedClassInstantiations();
1✔
196

197
        if (testedClasses != null) {
1✔
198
            TestRun.enterNoMockingZone();
1✔
199

200
            try {
201
                testedClasses.assignNewInstancesToTestedFields(testClassInstance, true,
1✔
202
                        Collections.<InjectionProvider> emptyList());
1✔
203
            } finally {
204
                TestRun.exitNoMockingZone();
1✔
205
            }
206
        }
207
    }
1✔
208

209
    protected static void createInstancesForTestedFields(@NonNull Object testClassInstance) {
210
        TestedClassInstantiations testedClasses = TestRun.getTestedClassInstantiations();
1✔
211

212
        if (testedClasses != null) {
1✔
213
            List<? extends InjectionProvider> injectableParameters = Collections.emptyList();
1✔
214
            ParameterTypeRedefinitions paramTypeRedefs = TestRun.getExecutingTest().getParameterRedefinitions();
1✔
215

216
            if (paramTypeRedefs != null) {
1✔
217
                injectableParameters = paramTypeRedefs.getInjectableParameters();
1✔
218
            }
219

220
            TestRun.enterNoMockingZone();
1✔
221

222
            try {
223
                testedClasses.assignNewInstancesToTestedFields(testClassInstance, false, injectableParameters);
1✔
224
            } finally {
225
                TestRun.exitNoMockingZone();
1✔
226
            }
227
        }
228
    }
1✔
229

230
    @Nullable
231
    protected static Object[] createInstancesForAnnotatedParameters(@NonNull Object testClassInstance,
232
            @NonNull Method testMethod, @Nullable Object[] parameterValues) {
233
        int numParameters = getParameterCount(testMethod);
1✔
234

235
        if (numParameters == 0) {
1✔
236
            return null;
1✔
237
        }
238

239
        if (parameterValues == null || parameterValues.length != numParameters) {
1!
240
            // noinspection AssignmentToMethodParameter
241
            parameterValues = new Object[numParameters];
1✔
242
        }
243

244
        TestMethod methodInfo = new TestMethod(testMethod, parameterValues);
1✔
245

246
        TestRun.enterNoMockingZone();
1✔
247

248
        try {
249
            ParameterTypeRedefinitions redefinitions = new ParameterTypeRedefinitions(methodInfo, parameterValues);
1✔
250
            TestRun.getExecutingTest().setParameterRedefinitions(redefinitions);
1✔
251

252
            TestedParameters testedParameters = new TestedParameters(methodInfo);
1✔
253
            List<? extends InjectionProvider> injectableParameters = redefinitions.getInjectableParameters();
1✔
254
            testedParameters.createTestedParameters(testClassInstance, injectableParameters);
1✔
255
        } finally {
256
            TestRun.exitNoMockingZone();
1✔
257
        }
258

259
        return parameterValues;
1✔
260
    }
261

262
    protected static void concludeTestMethodExecution(@NonNull SavePoint savePoint, @Nullable Throwable thrownByTest,
263
            boolean thrownAsExpected) throws Throwable {
264
        TestRun.enterNoMockingZone();
1✔
265

266
        Error expectationsFailure = RecordAndReplayExecution.endCurrentReplayIfAny();
1✔
267

268
        try {
269
            clearTestedObjectsIfAny();
1✔
270
        } finally {
271
            savePoint.rollback();
1✔
272
            TestRun.exitNoMockingZone();
1✔
273
        }
274

275
        if (thrownByTest != null) {
1!
UNCOV
276
            if (expectationsFailure == null || !thrownAsExpected || isUnexpectedOrMissingInvocation(thrownByTest)) {
×
UNCOV
277
                throw thrownByTest;
×
278
            }
279

280
            Throwable expectationsFailureCause = expectationsFailure.getCause();
×
281

282
            if (expectationsFailureCause != null) {
×
283
                expectationsFailureCause.initCause(thrownByTest);
×
284
            }
285
        }
286

287
        if (expectationsFailure != null) {
1!
UNCOV
288
            throw expectationsFailure;
×
289
        }
290
    }
1✔
291

292
    protected static void clearTestedObjectsIfAny() {
293
        TestedClassInstantiations testedClasses = TestRun.getTestedClassInstantiations();
1✔
294

295
        if (testedClasses != null) {
1✔
296
            testedClasses.clearTestedObjects();
1✔
297
        }
298
    }
1✔
299

300
    protected static void clearTestedObjectsCreatedDuringSetup() {
301
        TestedClassInstantiations testedClasses = TestRun.getTestedClassInstantiations();
1✔
302

303
        if (testedClasses != null) {
1✔
304
            testedClasses.clearTestedObjectsCreatedDuringSetup();
1✔
305
        }
306
    }
1✔
307

308
    private static boolean isUnexpectedOrMissingInvocation(@NonNull Throwable error) {
UNCOV
309
        Class<?> errorType = error.getClass();
×
UNCOV
310
        return errorType == UnexpectedInvocation.class || errorType == MissingInvocation.class;
×
311
    }
312
}
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