PnP.Core中IFolderCollection模拟失败:SharePoint文档库Mock异常排查
我需要模拟一个返回6个文件的SharePoint文档库,为此编写了SharePointDocLibraryUtilsTestFactory类构建Mock系统,但调试时发现mockFolderCollection为空,调用被测方法ProcessFolderAsync时传入的folder参数为null,引发空引用错误。
1. 测试工厂代码
public class SharePointDocLibraryUtilsTestFactory { private Mock<IPnPCoreSDKClient> _mockPnPCoreSDKClient; private Mock<IPnPContext> _mockPnpContext; private ITestOutputHelper _output; public SharePointDocLibraryUtilsTestFactory(ITestOutputHelper output) { _mockPnPCoreSDKClient = new Mock<IPnPCoreSDKClient>(); _mockPnpContext = new Mock<IPnPContext>(); _mockPnPCoreSDKClient.Setup(client => client.GetPnPContextAsync(It.IsAny<string>())).ReturnsAsync(_mockPnpContext.Object); _output = output; } public IList SetupMockSharePointLibrary() { var mockList = new Mock<IList>(); var mockRootFolder = new Mock<IFolder>(); var mockWeb = new Mock<IWeb>(); var subfolders = new List<IFolder>(); for (int i = 0; i < 3; i++) { var mockFolder = new Mock<IFolder>(); var mockFileCollection = new Mock<IFileCollection>(); var mockFiles = new List<IFile>(); for (int j = 0; j < 2; j++) { var mockFile = new Mock<IFile>(); mockFile.Setup(f => f.Name).Returns($"File_{i}_{j}.txt"); mockFile.Setup(f => f.ServerRelativeUrl).Returns($"/sites/me/Documents/folder_{i}/File_{i}_{j}.txt"); mockFiles.Add(mockFile.Object); } mockFileCollection.Setup(m => m.GetEnumerator()).Returns(() => mockFiles.GetEnumerator()); mockFolder.Setup(f => f.Files).Returns(mockFileCollection.Object); subfolders.Add(mockFolder.Object); } // Setup folder collection to return our mock folders var mockFolderCollection = new Mock<IFolderCollection>(); mockFolderCollection.Setup(m => m.GetEnumerator()).Returns(() => subfolders.GetEnumerator()); mockRootFolder.Setup(f => f.Folders).Returns(mockFolderCollection.Object); // Setup the mock list to return the root folder and the correct item count mockList.Setup(l => l.RootFolder).Returns(mockRootFolder.Object); // Setup the mock web to return the mock list mockWeb.Setup(web => web.Lists.GetByTitleAsync(It.IsAny<string>(), It.IsAny<Expression<Func<IList, object>>>())) .ReturnsAsync(mockList.Object); //mockList.Setup(l => l.RootFolder).Returns(mockRootFolder.Object); //mockWeb.Setup(web => web.Lists.GetByTitleAsync(It.IsAny<string>(), It.IsAny<Expression<Func<IList, object>>>())) // .ReturnsAsync(mockList.Object); _output.WriteLine($"Total file list count {mockList.Object.ItemCount}"); return mockList.Object; } }
2. 调试即时窗口输出
mockFolderCollection {Mock<IFolderCollection:1>} Behavior: Default CallBase: false DefaultValue: Empty DefaultValueProvider: {Moq.EmptyDefaultValueProvider} Invocations: {Moq.InvocationCollection} Name: "Mock<IFolderCollection:1>" Object (Moq.Mock): {Mock<IFolderCollection:1>.Object} Object: {Mock<IFolderCollection:1>.Object} Setups: {Moq.SetupCollection} Switches: Default
3. 被测方法代码
private async Task ProcessFolderAsync(IFolder folder, SharePointDocLibraryInventory result, bool recursive, string checksumType) { // Process files in the current folder foreach (var file in folder.Files) { string checksum = await GenerateChecksumAsync(file, checksumType); result.Files.Add(new SynchronizedSharePointDocument { Name = file.Name, FullPath = file.ServerRelativeUrl, RelativePath = file.ServerRelativeUrl.Replace(folder.ServerRelativeUrl, "").TrimStart('/'), Checksum = checksum }); result.TotalFiles++; } // Optionally process subfolders recursively if (recursive) { await folder.LoadAsync(f => f.Folders); foreach (var subfolder in folder.Folders) { result.TotalSubdirectories++; await ProcessFolderAsync(subfolder, result, recursive, checksumType); } } }
问题分析与修复方案
核心问题
- 未处理
LoadAsync的Mock设置:被测方法中调用了await folder.LoadAsync(f => f.Folders),但原Mock未定义该方法的行为,导致加载后文件夹集合未正确初始化。 - PnP对象未关联到Context:PnP Core的对象(如
IFolder、IList)需要关联到IPnPContext才能正确执行异步操作,原代码未设置这一关联。 - 文件夹集合Mock不完整:仅设置了枚举器,未处理集合的其他必要属性(如
Count),且未确保子文件夹的Folders属性返回空集合。
修改后的测试工厂代码
public IList SetupMockSharePointLibrary() { var mockList = new Mock<IList>(); var mockRootFolder = new Mock<IFolder>(); var mockWeb = new Mock<IWeb>(); var subfolders = new List<IFolder>(); // 关联根文件夹到PnP Context mockRootFolder.Setup(f => f.Context).Returns(_mockPnpContext.Object); for (int i = 0; i < 3; i++) { var mockFolder = new Mock<IFolder>(); var mockFileCollection = new Mock<IFileCollection>(); var mockFiles = new List<IFile>(); // 关联子文件夹到PnP Context mockFolder.Setup(f => f.Context).Returns(_mockPnpContext.Object); for (int j = 0; j < 2; j++) { var mockFile = new Mock<IFile>(); mockFile.Setup(f => f.Name).Returns($"File_{i}_{j}.txt"); mockFile.Setup(f => f.ServerRelativeUrl).Returns($"/sites/me/Documents/folder_{i}/File_{i}_{j}.txt"); mockFiles.Add(mockFile.Object); } // 设置文件集合的枚举器 mockFileCollection.Setup(m => m.GetEnumerator()).Returns(() => mockFiles.GetEnumerator()); mockFolder.Setup(f => f.Files).Returns(mockFileCollection.Object); // 设置子文件夹的空子目录集合及LoadAsync行为 var emptySubfolderCollection = new Mock<IFolderCollection>(); emptySubfolderCollection.Setup(m => m.GetEnumerator()).Returns(() => new List<IFolder>().GetEnumerator()); mockFolder.Setup(f => f.Folders).Returns(emptySubfolderCollection.Object); mockFolder.Setup(f => f.LoadAsync(It.IsAny<Expression<Func<IFolder, object>>>(), It.IsAny<CancellationToken>())) .Returns(Task.CompletedTask); subfolders.Add(mockFolder.Object); } // 设置根文件夹的子目录集合 var mockFolderCollection = new Mock<IFolderCollection>(); mockFolderCollection.Setup(m => m.GetEnumerator()).Returns(() => subfolders.GetEnumerator()); mockFolderCollection.Setup(m => m.Count).Returns(subfolders.Count); // 设置根文件夹的Folders属性及LoadAsync行为 mockRootFolder.Setup(f => f.Folders).Returns(mockFolderCollection.Object); mockRootFolder.Setup(f => f.LoadAsync(It.IsAny<Expression<Func<IFolder, object>>>(), It.IsAny<CancellationToken>())) .Returns(Task.CompletedTask); // 设置列表的根文件夹及Context关联 mockList.Setup(l => l.RootFolder).Returns(mockRootFolder.Object); mockList.Setup(l => l.Context).Returns(_mockPnpContext.Object); // 设置Web返回列表及Context关联 mockWeb.Setup(web => web.Lists.GetByTitleAsync(It.IsAny<string>(), It.IsAny<Expression<Func<IList, object>>>(), It.IsAny<CancellationToken>())) .ReturnsAsync(mockList.Object); mockWeb.Setup(w => w.Context).Returns(_mockPnpContext.Object); // 关联Web到PnP Context _mockPnpContext.Setup(c => c.Web).Returns(mockWeb.Object); _output.WriteLine($"Total file list count {mockList.Object.ItemCount}"); return mockList.Object; }
关键修复点
- 所有PnP对象(
IWeb、IList、IFolder)都关联到Mock的IPnPContext,确保异步方法正常触发Mock行为。 - 为
IFolder的LoadAsync方法设置空实现,避免调用时抛出异常。 - 为子文件夹设置空的子目录集合,防止递归处理时出现空引用。
- 补充文件夹集合的
Count属性设置,覆盖更多接口行为。
内容的提问来源于stack exchange,提问作者dot
相关产品推荐
相关产品推荐

