Mockito单元测试中XSSFWorkbook读取云存储文件为空的解决方法
问题解决:单元测试中让XSSFWorkbook读取到有效云存储文件内容
核心问题分析
你的测试代码存在几个关键问题导致XSSFWorkbook获取空内容:
- 提前实例化
XSSFWorkbook消耗了测试文件流,导致被测代码调用时流已处于EOF状态 - 未正确Mock
ReadChannel的read方法(NIO通道的核心读取逻辑),Channels.newInputStream包装后无法读取到实际数据 - 静态Mock的设置和被测代码执行的顺序存在逻辑问题
修正后的测试代码示例
import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; import com.google.cloud.storage.BlobId; import com.google.cloud.storage.Storage; import com.google.cloud.storage.StorageOptions; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; public class YourExcelReaderTest { private static final int TEST_FILE_SIZE = 2048; // 替换为实际测试文件大小 @Test void testReadExcelFromCloudStorage() throws Exception { // 1. 读取测试Excel文件为字节数组,避免流被提前消耗 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); try (InputStream testFileStream = classLoader.getResourceAsStream("mapping.xlsx")) { byte[] excelFileBytes = testFileStream.readAllBytes(); // 2. Mock核心依赖对象 Storage mockStorage = Mockito.mock(Storage.class); ReadableByteChannel mockReadChannel = Mockito.mock(ReadableByteChannel.class); // 3. Mock ReadChannel的read方法,模拟实际读取逻辑 ByteBuffer fileBuffer = ByteBuffer.wrap(excelFileBytes); when(mockReadChannel.read(any(ByteBuffer.class))).thenAnswer(invocation -> { ByteBuffer targetBuffer = invocation.getArgument(0); int availableBytes = Math.min(fileBuffer.remaining(), targetBuffer.remaining()); if (availableBytes == 0) { return -1; // 标记流结束 } targetBuffer.put(fileBuffer.array(), fileBuffer.position(), availableBytes); fileBuffer.position(fileBuffer.position() + availableBytes); return availableBytes; }); // 4. 管理静态类Mock的作用域 try (MockedStatic<StorageOptions> storageOptionsMock = Mockito.mockStatic(StorageOptions.class, Mockito.RETURNS_DEEP_STUBS); MockedStatic<java.nio.channels.Channels> channelsMock = Mockito.mockStatic(java.nio.channels.Channels.class)) { // 模拟Storage实例的获取逻辑 storageOptionsMock.when(() -> StorageOptions.newBuilder().setProjectId("Test").build().getService()) .thenReturn(mockStorage); // 模拟Storage.reader返回Mock通道 when(mockStorage.reader(any(BlobId.class))).thenReturn(mockReadChannel); // 模拟Channels.newInputStream返回有效流 channelsMock.when(() -> java.nio.channels.Channels.newInputStream(mockReadChannel)) .thenReturn(new ByteArrayInputStream(excelFileBytes)); // 5. 执行被测代码(替换为你实际的业务调用逻辑) try (ReadableByteChannel excelReader = mockStorage.reader(BlobId.of("test-bucket", "sample.xls")); InputStream inputStream = java.nio.channels.Channels.newInputStream(excelReader); XSSFWorkbook workbook = new XSSFWorkbook(inputStream)) { // 添加断言验证结果,例如检查工作表数量 assert workbook.getNumberOfSheets() > 0; } } } } }
关键修正点说明
- 避免流提前消耗:将测试文件转为字节数组,后续基于字节数组创建新的InputStream,确保每次使用都是完整的数据流
- Mock ReadChannel核心逻辑:NIO通道的读取依赖
read(ByteBuffer)方法,必须模拟该方法返回实际文件字节,否则包装后的InputStream会读取空内容 - 规范静态Mock作用域:用try-with-resources管理
MockedStatic,确保静态Mock仅在被测代码执行期间生效,执行后自动清理 - 移除无关Mock代码:如果业务逻辑不依赖Blob的元数据(如size、cacheControl等),可以删除这些不必要的Mock,简化测试代码
内容的提问来源于stack exchange,提问作者rakeeee
相关产品推荐
相关产品推荐

