使用Mockito.mockStatic模拟TemplateRendererUtil时触发类重定义失败错误咨询
Mockito模拟静态类TemplateRendererUtil遇class redefinition失败的问题解答
问题背景
尝试用Mockito模拟TemplateRendererUtil静态类以跳过实际执行,代码如下:
try (MockedStatic<TemplateRendererUtil> mockedStatic = Mockito.mockStatic(TemplateRendererUtil.class))
执行时抛出错误:
java.lang.InternalError: class redefinition failed: invalid class
被模拟类代码:
public class TemplateRendererUtil { private static final ResponseTemplateRenderer RENDERER = ResponseTemplateRenderer.getInstance(); private TemplateRendererUtil() { } public static void render(HttpServletRequest req, HttpServletResponse resp, String templateFile, Map<String, Object> params) throws TemplateRendererUtilException { try { RENDERER.render(req, resp, templateFile, params); } catch (ResponseTemplateRendererException var5) { throw new TemplateRendererUtilException(var5); } } }
完整堆栈跟踪:
java.lang.InternalError: class redefinition failed: invalid class at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses0(Native Method) at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:167) at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.triggerRetransformation(InlineBytecodeGenerator.java:280) at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.mockClassStatic(InlineBytecodeGenerator.java:225) at org.mockito.internal.creation.bytebuddy.TypeCachingBytecodeGenerator.mockClassStatic(TypeCachingBytecodeGenerator.java:63) at org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker.createStaticMock(InlineDelegateByteBuddyMockMaker.java:560) at org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker.createStaticMock(InlineByteBuddyMockMaker.java:83) at org.mockito.internal.util.MockUtil.createStaticMock(MockUtil.java:147) at org.mockito.internal.MockitoCore.mockStatic(MockitoCore.java:142) at org.mockito.Mockito.mockStatic(Mockito.java:2181) at org.mockito.Mockito.mockStatic(Mockito.java:2118) at com.nnixon.MySecondAdapterTest.testLookUpAuthWithInvalidAttributes(MySecondAdapterTest.java:107) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:727) at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60) at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131) at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:156) at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:147) at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:86) at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(InterceptingExecutableInvoker.java:103) at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0(InterceptingExecutableInvoker.java:93) at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106) at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64) at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45) at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37) at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:92) at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:86) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:217) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:213) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:138) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:68) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) at java.base/java.util.ArrayList.forEach(ArrayList.java:1541) at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) at java.base/java.util.ArrayList.forEach(ArrayList.java:1541) at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35) at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:147) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:127) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:90) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:55) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:102) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:54) at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114) at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86) at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86) at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53) at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57) at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38) at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11) at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35) at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235) at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
当前模拟方法是否可行?
理论上Mockito的mockStatic支持静态类模拟,但你遇到的错误是JVM层面的类重定义失败,核心原因是TemplateRendererUtil中的RENDERER是类加载阶段就初始化的静态常量,Mockito的内联模拟机制需要修改类字节码,但静态常量初始化后无法兼容JVM允许的字节码修改规则,导致校验报错。
虽然可以尝试升级Mockito版本、调整JVM参数(如-XX:-UseSplitVerifier)规避,但这类方案稳定性差,容易因环境变化失效,不建议作为长期解决方案。
更简便的替代方案
方案1:重构代码解耦(最优选择)
放弃静态工具类的设计,改用依赖注入实现解耦,彻底避免静态mock的麻烦:
- 定义渲染接口:
public interface TemplateRenderer { void render(HttpServletRequest req, HttpServletResponse resp, String templateFile, Map<String, Object> params) throws ResponseTemplateRendererException; }
- 让原实现类实现该接口:
public class ResponseTemplateRenderer implements TemplateRenderer { private static final ResponseTemplateRenderer INSTANCE = new ResponseTemplateRenderer(); public static ResponseTemplateRenderer getInstance() { return INSTANCE; } @Override public void render(HttpServletRequest req, HttpServletResponse resp, String templateFile, Map<String, Object> params) throws ResponseTemplateRendererException { // 原渲染逻辑 } }
- 将工具类改为可注入的服务类:
public class TemplateRendererService { private final TemplateRenderer renderer; // 构造注入,生产环境传入ResponseTemplateRenderer.getInstance() public TemplateRendererService(TemplateRenderer renderer) { this.renderer = renderer; } public void render(HttpServletRequest req, HttpServletResponse resp, String templateFile, Map<String, Object> params) throws TemplateRendererUtilException { try { renderer.render(req, resp, templateFile, params); } catch (ResponseTemplateRendererException var5) { throw new TemplateRendererUtilException(var5); } } }
测试时直接mockTemplateRenderer接口即可,完全不需要处理静态类:
@Test void testLookUpAuthWithInvalidAttributes() { TemplateRenderer mockRenderer = Mockito.mock(TemplateRenderer.class); TemplateRendererService service = new TemplateRendererService(mockRenderer); // 定义mock行为 Mockito.doNothing().when(mockRenderer).render(Mockito.any(), Mockito.any(), Mockito.anyString(), Mockito.anyMap()); // 执行测试逻辑 // ... }
方案2:PowerMock辅助(仅用于无法重构的老代码)
如果无法修改原代码,可使用PowerMock配合Mockito,它通过自定义类加载器绕过JVM的类重定义限制:
- 添加PowerMock依赖(需对应Mockito版本)
- 编写测试代码:
@RunWith(PowerMockRunner.class) @PrepareForTest(TemplateRendererUtil.class) public class MySecondAdapterTest { @Test public void testLookUpAuthWithInvalidAttributes() { PowerMock.mockStatic(TemplateRendererUtil.class); // 定义mock行为,跳过render方法执行 Mockito.doNothing().when(TemplateRendererUtil.class); TemplateRendererUtil.render(Mockito.any(), Mockito.any(), Mockito.anyString(), Mockito.anyMap()); // 执行测试逻辑 // ... } }
注意:PowerMock会改变类加载机制,可能和某些框架、JVM版本存在兼容性问题,优先选择方案1。
内容的提问来源于stack exchange,提问作者nixon
相关产品推荐
相关产品推荐

