You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jacoco与Mockito集成报NoClassDefFoundError,如何正常测并生成覆盖率报告?

问题描述

我有一个使用jacoco-maven-plugin、maven-surefire-plugin和Mockito的项目,配置如下:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
</plugin>
<plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>${jacoco-maven-plugin.version}</version>
    <executions>
        <execution>
            <id>prepare-agent</id>
            <goals>
                <goal>prepare-agent</goal>
            </goals>
        </execution>
        <execution>
            <id>report</id>
            <phase>prepare-package</phase>
            <goals>
                <goal>report</goal>
            </goals>
        </execution>
    </executions>
</plugin>

运行测试时出现如下错误:

java.lang.NoClassDefFoundError: com.foo.Foo$MockitoMock$yT0N0ogW (initialization failure)
        at java.base/java.lang.J9VMInternals.initializationAlreadyFailed(J9VMInternals.java:156) ~[na:na]
        at java.base/jdk.internal.misc.Unsafe.allocateInstance(Native Method) ~[na:na]
        at jdk.unsupported/sun.misc.Unsafe.allocateInstance(Unsafe.java:864) ~[na:na]
        at org.objenesis.instantiator.sun.UnsafeFactoryInstantiator.newInstance(UnsafeFactoryInstantiator.java:47) ~[objenesis-3.3.jar:3.3]
        at org.objenesis.ObjenesisBase.newInstance(ObjenesisBase.java:73) ~[objenesis-3.3.jar:3.3]
        at org.mockito.internal.creation.instance.ObjenesisInstantiator.newInstance(ObjenesisInstantiator.java:22) ~[mockito-core-5.3.1.jar:na]
        at org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker.doCreateMock(InlineDelegateByteBuddyMockMaker.java:376) ~[mockito-core-5.3.1.jar:na]
        at org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker.createMock(InlineDelegateByteBuddyMockMaker.java:334) ~[mockito-core-5.3.1.jar:na]
        at org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker.createMock(InlineByteBuddyMockMaker.java:56) ~[mockito-core-5.3.1.jar:na]
        at org.mockito.internal.util.MockUtil.createMock(MockUtil.java:99) ~[mockito-core-5.3.1.jar:na]
        at org.mockito.internal.MockitoCore.mock(MockitoCore.java:88) ~[mockito-core-5.3.1.jar:na]
        at org.mockito.Mockito.mock(Mockito.java:2101) ~[mockito-core-5.3.1.jar:na]
        at org.springframework.boot.test.mock.mockito.SpyDefinition.createSpy(SpyDefinition.java:116) ~[spring-boot-test-3.1.1.jar:3.1.1]
        at org.springframework.boot.test.mock.mockito.MockitoPostProcessor.createSpyIfNecessary(MockitoPostProcessor.java:331) ~[spring-boot-test-3.1.1.jar:3.1.1]
        at org.springframework.boot.test.mock.mockito.MockitoPostProcessor$SpyPostProcessor.postProcessAfterInitialization(MockitoPostProcessor.java:465) ~[spring-boot-test-3.1.1.jar:3.1.1]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:434) ~[spring-beans-6.0.10.jar:6.0.10]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.postProcessObjectFromFactoryBean(AbstractAutowireCapableBeanFactory.java:1885) ~[spring-beans-6.0.10.jar:6.0.10]
        at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:105) ~[spring-beans-6.0.10.jar:6.0.10]
        ... 121 common frames omitted
Caused by: java.lang.NullPointerException: null
        at java.base/java.util.ArrayList.add(ArrayList.java:453) ~[na:na]
        at java.base/java.util.ArrayList.add(ArrayList.java:467) ~[na:na]
        at java.base/java.lang.invoke.MethodTypeHelper.parseIntoClass(MethodTypeHelper.java:237) ~[na:na]
        at java.base/java.lang.invoke.MethodHandleResolver.fromFieldDescriptorString(MethodHandleResolver.java:306) ~[na:na]
        at java.base/java.lang.invoke.MethodHandleResolver.resolveConstantDynamic(MethodHandleResolver.java:102) ~[na:na]
        at com.foo.Foo$MockitoMock$yT0N0ogW.<clinit>(Unknown Source) ~[classes/:na]
        at java.base/jdk.internal.misc.Unsafe.allocateInstance(Native Method) ~[na:na]
        at java.base/java.lang.invoke.DirectMethodHandle.allocateInstance(DirectMethodHandle.java:520) ~[na:na]
        at java.base/java.lang.invoke.MethodHandle.invokeWithArguments(MethodHandle.java:732) ~[na:na]
        at org.mockito.internal.util.reflection.InstrumentationMemberAccessor$Dispatcher$ByteBuddy$vQ02mWmG.invokeWithArguments(Unknown Source) ~[na:na]
        at org.mockito.internal.util.reflection.InstrumentationMemberAccessor.lambda$newInstance$0(InstrumentationMemberAccessor.java:199) ~[mockito-core-5.3.1.jar:na]
        at org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker.lambda$newInstance$4(InlineDelegateByteBuddyMockMaker.java:636) ~[mockito-core-5.3.1.jar:na]
        at org.mockito.internal.util.reflection.InstrumentationMemberAccessor.newInstance(InstrumentationMemberAccessor.java:188) ~[mockito-core-5.3.1.jar:na]
        at org.mockito.internal.util.reflection.ModuleMemberAccessor.newInstance(ModuleMemberAccessor.java:49) ~[mockito-core-5.3.1.jar:na]
        at org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker.newInstance(InlineDelegateByteBuddyMockMaker.java:631) ~[mockito-core-5.3.1.jar:na]
        at org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker.doCreateMock(InlineDelegateByteBuddyMockMaker.java:369) ~[mockito-core-5.3.1.jar:na]
        ... 132 common frames omitted

尝试在surefire插件配置中添加<argLine>...</argLine>后,测试可正常运行,但无法生成jacoco.exec文件及Jacoco覆盖率报告。请问该错误产生的原因是什么?如何才能让测试正常运行同时生成Jacoco覆盖率报告?


问题分析与解决方案

错误原因

这个错误是Jacoco的prepare-agent目标注入的JVM参数与Mockito默认的内联模拟机制冲突导致的:

  • Jacoco的prepare-agent会自动给surefire插件设置argLine参数,加入覆盖率采集的JVM代理(-javaagent:jacocoagent.jar)
  • Mockito 5+默认启用内联模拟(Inline Mock Maker),这种机制会动态生成字节码创建模拟类。Jacoco代理在处理这些动态生成的Mockito模拟类时,会触发JVM内部的空指针异常,导致模拟类初始化失败,最终抛出NoClassDefFoundError。

手动配置surefire的argLine时,会覆盖Jacoco自动注入的代理参数,导致Jacoco无法采集覆盖率数据,所以测试能正常运行,但生成不了报告。

解决方法

方法一:保留Jacoco参数,不覆盖argLine

修改surefire插件配置,引用Jacoco生成的argLine变量,同时添加自定义参数(如果需要):

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
        <!-- 引用Jacoco注入的参数,避免覆盖,可追加自定义JVM参数 -->
        <argLine>${argLine}</argLine>
        <!-- 显式指定Jacoco输出文件路径,确保生成位置正确 -->
        <systemPropertyVariables>
            <jacoco-agent.destfile>${project.build.directory}/jacoco.exec</jacoco-agent.destfile>
        </systemPropertyVariables>
    </configuration>
</plugin>

这样既保留了Jacoco的覆盖率采集代理,又不会产生冲突,测试和报告生成都能正常工作。

方法二:切换Mockito模拟生成方式(备选)

如果方法一无效,可以强制Mockito使用子类模拟(Subclass Mock Maker),避免与Jacoco的字节码修改冲突:

  1. 在src/test/resources/mockito-extensions/目录下创建文件org.mockito.plugins.MockMaker,内容为:
mock-maker-subclass
  1. 或者通过JVM参数指定:
<argLine>${argLine} -Dmockito.mock-maker=subclass</argLine>

注意:这种方式会限制Mockito的功能(比如无法模拟final类、静态方法、私有方法等),仅作为备选方案。

方法三:确保版本兼容

使用Jacoco 0.8.8及以上版本,它对Mockito 5+的兼容性更好,建议升级Jacoco插件到最新稳定版。

验证结果

执行Maven命令:

mvn clean test jacoco:report

测试正常运行后,会在target目录下生成jacoco.exec文件,HTML格式的覆盖率报告位于target/site/jacoco目录下。


内容的提问来源于stack exchange,提问作者NickNgn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 09:16:57