如何Mock ExcelReaderFactory.CreateReader并解决xUnit+Moq测试报错
解决xUnit+Moq测试Excel读取时的编码、Mock类型和文件签名问题
咱们一步步拆解你遇到的三个问题,逐个解决:
1. 先搞定「No data is available for encoding 1252」错误
这个问题是因为.NET Core/.NET 5+之后,默认不再包含Windows专属的旧编码(比如1252),需要手动引入并注册:
- 先给测试项目安装NuGet包:
System.Text.Encoding.CodePages - 在测试类的构造函数或者测试初始化代码里添加编码注册:
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
2. 修复「Object cannot be converted to IExcelDataReader」的Mock错误
你之前的Mock Returns写法搞错了泛型参数,Returns<IExcelDataReader>的Func参数应该对应输入的MemoryStream,而不是返回类型。调整成这样:
mockExcelDataMock.Setup(data => data.CreateReader(It.IsAny<MemoryStream>())) .Returns((MemoryStream _) => { var mockReader = new Mock<IExcelDataReader>(); // 这里可以按需设置mockReader的行为,比如返回表头、数据等 return mockReader.Object; });
或者更简洁的写法:
mockExcelDataMock.Setup(data => data.CreateReader(It.IsAny<MemoryStream>())) .Returns(() => new Mock<IExcelDataReader>().Object);
3. 解决「invalid file signature」错误
这个问题的核心是:你测试里的MemoryStream写的是纯文本"sample data",根本不是有效的Excel文件格式,ExcelReaderFactory自然会报错。
单元测试的本质是隔离依赖,咱们完全不需要让代码真的去解析Excel文件,应该把Excel读取的逻辑彻底Mock掉。这里建议你重构一下ExcelData类,把静态依赖的ExcelReaderFactory抽象成接口,方便Mock:
第一步:抽象Excel读取依赖
创建一个接口和实现类,把静态的ExcelReaderFactory包装起来:
// 抽象接口 public interface IExcelReaderFactory { IExcelDataReader CreateReader(Stream stream); } // 真实环境的实现 public class ExcelReaderFactoryWrapper : IExcelReaderFactory { public IExcelDataReader CreateReader(Stream stream) { return ExcelReaderFactory.CreateReader(stream); } }
第二步:修改ExcelData类,依赖注入接口
把原来直接调用静态方法的代码改成依赖注入:
public class ExcelData { private readonly IExcelReaderFactory _excelReaderFactory; // 通过构造函数注入依赖 public ExcelData(IExcelReaderFactory excelReaderFactory) { _excelReaderFactory = excelReaderFactory; } // 注意:异步方法要加async和await,否则流可能没写完就被读取了! public async Task<DataTable> ReadFromExcel(CloudBlockBlob MasterDataSourceBlob, string SheetName) { DataTable dataInExcelSheet = new DataTable(); using (var memoryStream = new MemoryStream()) { await MasterDataSourceBlob.DownloadToStreamAsync(memoryStream); memoryStream.Position = 0; // 重置流的位置,否则会从末尾开始读取 var headers = new List<string>(); IExcelDataReader excelReader = _excelReaderFactory.CreateReader(memoryStream); // 这里补充你的Excel解析逻辑,比如转成DataTable // 示例: // var dataSet = excelReader.AsDataSet(new ExcelDataSetConfiguration // { // ConfigureDataTable = _ => new ExcelDataTableConfiguration { UseHeaderRow = true } // }); // dataInExcelSheet = dataSet.Tables[SheetName]; } return dataInExcelSheet; } }
第三步:调整测试代码,完全Mock依赖
现在测试时不需要真实的Excel文件,直接MockIExcelDataReader返回预设数据:
[Fact] public async Task ReadFromExcel_Success() { // 注册编码解决1252问题 Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); // 1. Mock IExcelDataReader,预设它的行为 var mockExcelReader = new Mock<IExcelDataReader>(); // 模拟表头和数据行 mockExcelReader.Setup(r => r.FieldCount).Returns(2); mockExcelReader.SetupSequence(r => r.Read()) .Returns(true) // 表头行 .Returns(true) // 第一行数据 .Returns(false); // 读取结束 mockExcelReader.Setup(r => r.GetName(0)).Returns("ID"); mockExcelReader.Setup(r => r.GetName(1)).Returns("Name"); mockExcelReader.Setup(r => r.GetInt32(0)).Returns(1); mockExcelReader.Setup(r => r.GetString(1)).Returns("TestUser"); // 2. Mock IExcelReaderFactory,返回上面的mockReader var mockReaderFactory = new Mock<IExcelReaderFactory>(); mockReaderFactory.Setup(f => f.CreateReader(It.IsAny<Stream>())) .Returns(mockExcelReader.Object); // 3. Mock CloudBlockBlob,模拟下载流的行为 var mockBlob = new Mock<CloudBlockBlob>(new Uri("https://samplestorage.blob.core.windows.net/samplecontainer")); mockBlob.Setup(b => b.DownloadToStreamAsync(It.IsAny<Stream>(), CancellationToken.None)) .Callback((Stream target, CancellationToken _) => { // 随便写点内容就行,因为不会真的解析 var writer = new StreamWriter(target); writer.Write("dummy content"); writer.Flush(); }) .Returns(Task.CompletedTask); // 4. 执行测试并断言 var excelData = new ExcelData(mockReaderFactory.Object); var result = await excelData.ReadFromExcel(mockBlob.Object, "Sheet1"); Assert.NotNull(result); Assert.Equal(2, result.Columns.Count); Assert.Equal("ID", result.Columns[0].ColumnName); Assert.Equal(1, result.Rows.Count); Assert.Equal(1, result.Rows[0]["ID"]); }
额外提醒
- 异步方法一定要用
await,否则会出现流未写入完成就被读取的问题,测试方法也要改成async Task。 - 单元测试要聚焦在你的业务逻辑,尽量不要依赖第三方库的真实功能,Mock掉所有外部依赖才是纯单元测试的正确姿势。
内容的提问来源于stack exchange,提问作者chandra sekhar
相关产品推荐
相关产品推荐

