使用Spock编写Java函数单元测试时遭遇org.apache.poi.EmptyFileException
Spock测试中XSSFWorkbook抛出
org.apache.poi.EmptyFileException的原因及修复 问题重现
测试用例(Spock):
def "should generate claims error file"(){ given: def tempFile = new TempFile() def excelErrorRecord = new ExcelErrorRecord(row: 5, col: 4, message: "Test Message") HashMap<String, List<ExcelErrorRecord>> errorRecords = new HashMap<>(); errorRecords.put("Medical",[excelErrorRecord]) def fileInputStream = Mock(FileInputStream) def workBook = Mock(Workbook) def file = Mock(File) and: tempFile.getFile() >> file file.length() >> 100 tempFile.getFile().getAbsolutePath() >> "Temp File.xlsx" new FileInputStream(tempFile.getFile().getAbsolutePath()) >> fileInputStream new XSSFWorkbook(fileInputStream) >> workBook when: service.generateClaimsErrorFile(errorRecords, tempFile) then: noExceptionThrown() }
被测Java代码片段:
try (FileInputStream fis = new FileInputStream(tempFile.getFile().getAbsolutePath()); Workbook workbook = new XSSFWorkbook(fis)) {} catch(Exception e){}
原因分析
抛出EmptyFileException的核心原因是:你Mock的FileInputStream对象没有任何可读的有效数据。XSSFWorkbook初始化时会尝试读取流内容,验证是否符合XLSX文件结构,当检测到流为空或无有效文件头时,就会抛出该异常。
测试中通过Spock构造方法Mock,让new FileInputStream(...)返回一个Mock实例,但这个Mock流未配置任何可读数据,POI库无法识别为有效Excel文件流,因此报错。
修复方案
有两种可行修复思路,可根据代码架构选择:
方案1:提供有效的Excel文件流
直接构造包含空XLSX文件内容的流,替换Mock的FileInputStream,让POI库能正常初始化Workbook:
def "should generate claims error file"(){ given: def tempFile = new TempFile() def excelErrorRecord = new ExcelErrorRecord(row: 5, col: 4, message: "Test Message") HashMap<String, List<ExcelErrorRecord>> errorRecords = new HashMap<>(); errorRecords.put("Medical",[excelErrorRecord]) def workBook = Mock(Workbook) def file = Mock(File) // 构造包含空XLSX内容的字节流 def tempXlsxOutput = new ByteArrayOutputStream() new XSSFWorkbook().write(tempXlsxOutput) def validInputStream = new ByteArrayInputStream(tempXlsxOutput.toByteArray()) and: tempFile.getFile() >> file file.length() >> 100 tempFile.getFile().getAbsolutePath() >> "Temp File.xlsx" // 让FileInputStream返回有效流 new FileInputStream(_) >> validInputStream new XSSFWorkbook(_) >> workBook when: service.generateClaimsErrorFile(errorRecords, tempFile) then: noExceptionThrown() }
方案2:重构代码,依赖注入Workbook创建逻辑
把Workbook的创建逻辑封装成独立依赖,通过构造方法注入到service中,测试时直接Mock该依赖,完全避开流处理:
第一步:重构Java代码
// 定义Workbook工厂接口 public interface WorkbookFactory { Workbook create(File file) throws IOException; } // 实现XSSF版本的工厂 public class XSSFWorkbookFactory implements WorkbookFactory { @Override public Workbook create(File file) throws IOException { try (FileInputStream fis = new FileInputStream(file.getAbsolutePath())) { return new XSSFWorkbook(fis); } } } // 修改Service类,注入工厂 public class ClaimsService { private final WorkbookFactory workbookFactory; // 构造方法注入 public ClaimsService(WorkbookFactory workbookFactory) { this.workbookFactory = workbookFactory; } public void generateClaimsErrorFile(Map<String, List<ExcelErrorRecord>> errorRecords, TempFile tempFile) { try { // 使用工厂创建Workbook Workbook workbook = workbookFactory.create(tempFile.getFile()); // 后续业务逻辑... } catch (IOException e) { // 异常处理... } } }
第二步:修改Spock测试用例
def "should generate claims error file"(){ given: def tempFile = new TempFile() def excelErrorRecord = new ExcelErrorRecord(row: 5, col: 4, message: "Test Message") HashMap<String, List<ExcelErrorRecord>> errorRecords = new HashMap<>(); errorRecords.put("Medical",[excelErrorRecord]) def workBook = Mock(Workbook) def file = Mock(File) // Mock工厂对象 def workbookFactory = Mock(WorkbookFactory) // 初始化service时注入Mock的工厂 def service = new ClaimsService(workbookFactory) and: tempFile.getFile() >> file workbookFactory.create(file) >> workBook when: service.generateClaimsErrorFile(errorRecords, tempFile) then: noExceptionThrown() }
内容的提问来源于stack exchange,提问作者Thiluxan
相关产品推荐
相关产品推荐

