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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 21:32:38