Mockito mock静态方法未生效报ExceptionInInitializerError问题
Mock静态方法抛出ExceptionInInitializerError且mock返回值不生效
问题根因
你这里有两个直接导致问题的错误,之前对mockito-inline的排查结论是错的:
- 类初始化失败是核心报错原因:
BatchesController的静态字段初始化逻辑有bug。代码里写了private static BatchesController batchesController = new BatchesController();,但你只定义了带BatchDao参数的构造方法,JVM不会生成默认无参构造,类加载阶段执行静态字段初始化时调用不存在的构造方法,直接抛出异常,触发ExceptionInInitializerError。 - 静态mock提前失效:你把
MockedStatic定义在@Before方法的try-with-resources块里,try块执行完就会自动调用close()关闭静态mock,等执行@Test方法时,静态mock已经被销毁,自然不会返回你预设的mock对象。 - mockito-inline完全支持普通类、final类的静态mock,不存在仅支持接口mock的限制,之前的报错和mockito-inline的能力无关。
修复方案
1. 先修复BatchesController的单例实现bug
原代码的静态字段显式初始化和懒汉单例逻辑完全冲突,而且单例构造方法没有私有化,修正后代码:
public class BatchesController { Logger log = Logger.getLogger(BatchesController.class.getName()); // 去掉错误的无参构造初始化 private static BatchesController batchesController; private final BatchDao batchDao; // 单例构造必须私有,避免外部实例化破坏单例 private BatchesController(BatchDao batchDao) { this.batchDao = batchDao; } public static BatchesController getInstance() { if (batchesController == null) { batchesController = new BatchesController(BatchDaoFactory.getDao()); } return batchesController; } // 其余业务方法 }
注意:如果你的
BatchesController不需要保持单例状态,更推荐把它改成普通类通过依赖注入传入使用,从根源上避免静态方法mock的成本。
2. 修正静态Mock的生命周期管理
JUnit4环境下MockedStatic不能提前关闭,需要把它的生命周期和测试类绑定,修正后的测试代码:
@RunWith(MockitoJUnitRunner.class) public class GetAllBatchesTest { @Mock ExecutionContext context; @Mock HttpRequestMessage<String> request; @Mock BatchesController batchesController; @Mock BatchDao batchDao; @InjectMocks GetAllBatches getAllBatchesMock = new GetAllBatches(); // 将MockedStatic定义为测试类字段,统一管理生命周期 private MockedStatic<BatchesController> batchesControllerStaticMock; @Before public void setUp() { Map<String, String> queryMap = new HashMap<>(); queryMap.put("connector", ""); doReturn(Logger.getGlobal()).when(context).getLogger(); doReturn(queryMap).when(request).getQueryParameters(); // 初始化静态mock,不要放在try-with-resources中提前关闭 batchesControllerStaticMock = Mockito.mockStatic(BatchesController.class); batchesControllerStaticMock.when(BatchesController::getInstance).thenReturn(batchesController); // 补全response builder的mock逻辑 doAnswer((Answer<HttpResponseMessage.Builder>) invocation -> { HttpStatus status = (HttpStatus) invocation.getArguments()[0]; return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); }).when(request).createResponseBuilder(any(HttpStatus.class)); } @After public void tearDown() { // 所有测试执行完成后再关闭静态mock batchesControllerStaticMock.close(); } @Test public void testHttpTriggerJava() { // 补全业务方法的mock返回,避免调用真实逻辑 doReturn(Collections.emptyList()).when(batchesController).getBatches(anyString()); final HttpResponseMessage result = getAllBatchesMock.run(request, context); Assertions.assertEquals(HttpStatus.OK, result.getStatus()); } }
补充说明
- 只要使用try-with-resources包裹
MockedStatic,它的生效范围就仅限try块内部,块结束立刻失效,不要在初始化阶段用这种写法。 - 如果后续升级到JUnit5,可以用
@MockitoSettings(strictness = Strictness.LENIENT)配合@MockStatic注解自动管理静态mock的生命周期,不用手动写close逻辑。
内容的提问来源于stack exchange,提问作者NITESH K.C
相关产品推荐
相关产品推荐

