GitLab CI单元测试失败:AnnualReturnRepository.save未被调用排查
问题场景
本地环境可正常执行AnnualReturnService的单元测试,但在GitLab CI中运行时失败,报错显示期望调用annualReturnRepository.save(任意AnnualReturn实例),但该Mock对象无任何交互。最初猜测是GitLab无数据库访问权限,排查代码后发现核心原因是业务类中直接实例化OcrUtility,导致测试中Mock的OcrUtility未生效,进而中断了后续save逻辑的执行。
GitLab报错信息
Error message: [ERROR] Failures: [ERROR] AnnualReturnServiceTest.testSave:102 Wanted but not invoked: annualReturnRepository.save( <any com.riskanalysisexpertsystem.scanpro.model.company.AnnualReturn> ); -> at com.test.unit.AnnualReturnServiceTest.testSave(AnnualReturnServiceTest.java:102) Actually, there were zero interactions with this mock.
相关代码片段
测试类代码(AnnualReturnServiceTest.testSave)
public class AnnualReturnServiceTest{ private Logger logger; private PDFtoImageConverterUtility pdftoImageConverterUtility; private final String SOURCE_FILE_PATH = "../scanpro/src/test/resources/files/taxdocuments/company/annual_return_form.pdf"; private final String SOURCE_DESTINATION_PATH = "../scanpro/src/test/resources/files/taxdocuments/company/annual_return.png"; @Mock private AnnualReturnRepository annualReturnRepository; @Mock private OcrUtility ocrUtility; @InjectMocks private AnnualReturnService annualReturnService; @BeforeClass public void setUpBeforeClass() throws Exception{ logger = LogManager.getLogger(AnnualReturnServiceTest.class); pdftoImageConverterUtility = new PDFtoImageConverterUtility(); MockitoAnnotations.openMocks(this); } public void testSave() { logger.info("Mock file and file details"); pdftoImageConverterUtility.convertFile(SOURCE_FILE_PATH, SOURCE_DESTINATION_PATH); File file = new File(SOURCE_DESTINATION_PATH); String fileDetails = "Annual Return Form\n" + "Company Name: Apple Inc.\n" + "\n" + "TIN: 3243243\n" + "\n" + "Company Address: 32432 MaryDale Drive., Ghana 234324\n" + "Company Registration Number: 432432\n" + "Fiscal Year Start Date: 2022-01\n" + "\n" + "Fiscal Year End Date: 2022-12\n" + "\n" + "Total Revenue: 342432\n" + "\n" + "Total Expenses: 3434\n" + "\n" + "Taxable Income: 34333\n" + "\n" + "Tax Paid: 3243"; logger.info("Mock the behaviour of OCR utility"); when(ocrUtility.performOCR(file)).thenReturn(fileDetails); logger.info("Call the save method"); annualReturnService.save(file); logger.info("Verify that parseAnnualReturnDetails is not null"); try { assertNotNull(annualReturnService.parseAnnualReturnDetails(fileDetails)); } catch (ParseException e) { // TODO Auto-generated catch block e.printStackTrace(); } logger.info("Verify the annualReturnRepository's save method is called"); verify(annualReturnRepository, times(1)).save(any(AnnualReturn.class)); } }
业务类代码(AnnualReturnService)
@Service @RequiredArgsConstructor public class AnnualReturnService { private final AnnualReturnRepository annualReturnRepository; public void save(File file) { OcrUtility ocrUtility = new OcrUtility(); String fileDetails = ocrUtility.performOCR(file); if (fileDetails != null) { AnnualReturn annualReturn = new AnnualReturn(); try { annualReturn = parseAnnualReturnDetails(fileDetails); } catch (ParseException e) { // TODO Auto-generated catch block e.printStackTrace(); } if (annualReturn != null) { annualReturnRepository.save(annualReturn); } } } }
.gitlab-ci配置
test: stage: test script: - mvn test -Dtest=com.test.unit.AnnualReturnServiceTest allow_failure: true dependencies: - build artifacts: paths: - target/surefire-reports expire_in: "30 days"
问题根源
业务类AnnualReturnService的save方法中直接通过new OcrUtility()实例化工具类,没有使用依赖注入的方式获取实例。这导致测试类中通过@Mock创建的OcrUtility对象完全没有被业务类使用,业务类实际调用的是真实的OcrUtility实例。在GitLab CI环境中,真实的OcrUtility执行performOCR可能返回null,后续的if (fileDetails != null)判断不成立,导致annualReturnRepository.save()根本不会被调用,最终触发测试失败的报错。
修复方案
修改业务类,实现OcrUtility的依赖注入
调整AnnualReturnService的代码,将OcrUtility改为通过构造函数注入:@Service @RequiredArgsConstructor public class AnnualReturnService { private final AnnualReturnRepository annualReturnRepository; private final OcrUtility ocrUtility; // 新增注入的OcrUtility public void save(File file) { String fileDetails = ocrUtility.performOCR(file); // 使用注入的实例,移除new操作 if (fileDetails != null) { AnnualReturn annualReturn = new AnnualReturn(); try { annualReturn = parseAnnualReturnDetails(fileDetails); } catch (ParseException e) { e.printStackTrace(); } if (annualReturn != null) { annualReturnRepository.save(annualReturn); } } } }优化测试类(可选)
测试中当前依赖真实的PDF转图片操作,在GitLab环境中可能因文件路径、资源可用性等问题导致不稳定。可以直接Mock File对象,避免真实文件操作:// 替换测试中的文件创建逻辑 File mockFile = Mockito.mock(File.class); // 调整OcrUtility的Mock逻辑 when(ocrUtility.performOCR(mockFile)).thenReturn(fileDetails); // 调用save方法时传入mockFile annualReturnService.save(mockFile);验证GitLab CI执行
提交修改后的代码,重新运行GitLab CI测试,确认报错消失,测试通过。
内容的提问来源于stack exchange,提问作者AppleEater

