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

EasyMock测试在GitLab流水线报错finalize()预期1实际0问题排查

问题描述

本地运行基于EasyMock的旧测试用例一切正常,但在GitLab流水线执行时抛出如下错误:

junit.framework.AssertionFailedError: 
  Expectation failure on verify:
    finalize(): expected: 1, actual: 0
    finalize(): expected: 1, actual: 0
    at org.easymock.internal.MocksControl.verify(MocksControl.java:184)
    at myproject.mocks.EasyMockTestCase.verify(EasyMockTestCase.java:72)
    at myproject.controllers.TestController.userIsAbleToSave(TestController.java:132)

该问题自logback.xml中添加Splunk的HTTP appender(即添加<appender-ref ref="http"/>)后出现,属于涉及finalize()方法的特殊场景。

相关业务代码

@RequestMapping(value = ACTION, params = "buttonSubmit")
@Secured(Permissions.UPDATE_INSTRUMENT)
public ModelAndView buttonSubmit(TypeRequest typeRequest, final HttpServletRequest request) throws Exception {
    ModelAndView mav = getView();
    Product product;
    try {
        product = productLifeCycleService.createNew(new AppRequest(request));
    } catch (DuplicateException e) {
        return handleDuplicate(typeRequest, request, false, e);
    } catch (ValidationException e) {
        return handleValidation(request, false, e);
    }
    return redirectToProductDetail(mav, product.getId());
}

测试代码

@Test
public void userIsAbleToSave() throws Exception {
    expect(productLifeCycleService.createNew((Request)anyObject())).andStubReturn(product);
    expect(product.getId()).andStubReturn(STANDARD);
    TypeRequest typeRequest = new TypeRequest (QQ, HH, GOAL);

    replay();
    underTest.buttonSubmit(typeRequest, httpRequest);
    verify();
}

HttpServletRequest模拟代码(@Before方法中)

private void mockHttpRequest() {
    httpRequest = mock(MockHttpServletRequest.class);
    expect(httpRequest.getRequestURI()).andStubReturn(URI);
    expect(httpRequest.getHeader(HEADER)).andStubReturn(null);
    expect(httpRequest.getParameter(TYPE)).andStubReturn(TypeName.LEGAL.getFormName());
    expect(httpRequest.getMethod()).andStubReturn(POST);
}

问题分析与解决方案

1. finalize()报错原因

EasyMock默认会模拟类的所有方法,包括finalize()。添加Splunk HTTP appender后,日志组件在后台可能触发了对象垃圾回收,导致被Mock的MockHttpServletRequest对象的finalize()方法被调用,但测试代码未为该方法设置预期,执行verify()时就会抛出预期次数不匹配的错误。

另外,MockHttpServletRequest作为Spring测试类,其finalize()方法在特定环境下易被触发,而Splunk的HTTP appender引入的额外线程或资源操作,会加速对象回收时机,进而触发该方法。

2. 本地与GitLab执行差异原因

  • JVM参数差异:GitLab流水线的JVM可能启用了更激进的GC策略,导致对象更快被回收,finalize()被触发;本地JVM因调试环境或宽松的GC参数,对象未被及时回收,finalize()未被调用。
  • 环境资源限制:GitLab流水线的容器环境资源有限,GC触发更频繁;本地环境资源充足,GC触发较少。
  • 日志组件行为差异:Splunk appender在不同环境下的初始化或线程行为不同,后台操作间接触发了对象回收。

3. 不切换Mockito的解决方案

方案一:仅模拟需要的方法,排除finalize()

创建Mock对象时,指定只模拟业务需要的方法,避免触发finalize()的预期检查:

// 方式1:指定要模拟的方法列表
httpRequest = mock(MockHttpServletRequest.class, MethodCaller.DEFAULT, 
                   new String[]{"getRequestURI", "getHeader", "getParameter", "getMethod"});

// 方式2:使用MockBuilder精准添加需要模拟的方法
httpRequest = createMockBuilder(MockHttpServletRequest.class)
    .addMockedMethod("getRequestURI")
    .addMockedMethod("getHeader")
    .addMockedMethod("getParameter")
    .addMockedMethod("getMethod")
    .createMock();

方案二:为finalize()添加预期

如果无法排除该方法,直接为finalize()设置允许任意次数调用的预期:

// 在mockHttpRequest()方法中添加
expect(httpRequest.finalize()).andStubAnswer(new IAnswer<Void>() {
    public Void answer() throws Throwable {
        return null;
    }
});

方案三:测试环境禁用Splunk appender

在测试专用的logback-test.xml中不添加Splunk的HTTP appender,避免触发相关后台操作:

<!-- 测试环境仅保留必要的控制台输出 -->
<root level="info">
    <appender-ref ref="CONSOLE"/>
</root>

该方案既不影响生产环境日志配置,也能从根源避免测试异常。

方案四:调整GitLab流水线的JVM参数

在流水线的测试执行命令中添加JVM参数,延迟GC触发:

-Djava.lang.ref.Finalizer.maxQueueSize=10000 -XX:+DisableExplicitGC

此方法治标不治本,仅作为临时应急方案。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 20:13:10