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的字节码修改冲突:
- 在
src/test/resources/mockito-extensions/目录下创建文件org.mockito.plugins.MockMaker,内容为:
mock-maker-subclass
- 或者通过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

