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

使用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的麻烦:

  1. 定义渲染接口:
public interface TemplateRenderer {
    void render(HttpServletRequest req, HttpServletResponse resp, String templateFile, Map<String, Object> params) throws ResponseTemplateRendererException;
}
  1. 让原实现类实现该接口:
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 {
        // 原渲染逻辑
    }
}
  1. 将工具类改为可注入的服务类:
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的类重定义限制:

  1. 添加PowerMock依赖(需对应Mockito版本)
  2. 编写测试代码:
@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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 23:18:42