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
相关产品推荐
相关产品推荐

