如何用Moq模拟Google StorageClient的ListObjects方法进行单元测试
如何对调用GCS ListObjects的GetFilesInGCS方法做单元测试
问题背景
我需要为GetFilesInGCS方法编写单元测试,该方法通过调用StorageClient.ListObjects获取GCS存储桶中的文件名。但ListObjects返回的是抽象类PageEnumerable<Objects, Object>,我不知道如何正确模拟它的返回值,目前已写出部分测试代码但未完成正确模拟。
待测试代码
public List<string> GetFilesInGCS(string bucket, string node) { List<string> fileNames = new List<string>(); try { log.Info($"Getting files from {bucket}..."); foreach (var storageObject in storageClient.ListObjects(bucket, node)) fileNames.Add(storageObject.Name); log.Info($"Total files: {fileNames.Count}"); } catch (Exception ex) { Task.Run(async () => { await _emailer.SendErrorAsync(ex); }); log.Error(ex); } return fileNames; }
已编写的测试代码
[Test] public void TestGetFilesInGCS() { Mock<StorageClient> storageClientMock = new Mock<StorageClient>(MockBehavior.Loose); storageClientMock.Setup(m => m.ListObjects(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<ListObjectsOptions>())); GoogleService g = new GoogleService(storageClientMock.Object); var files = g.GetFilesInGCS("test", "test"); Assert.That(files, Is.Not.Empty); }
解决方案
核心是让模拟的ListObjects返回可遍历的PageEnumerable<Objects, Object>实例,提供预设的测试数据。以下是两种可行方法:
方法一:手动实现PageEnumerable子类
因为PageEnumerable是抽象类,我们可以编写一个简单的子类,返回预设的对象集合:
public class MockPageEnumerable : PageEnumerable<Objects, Object> { private readonly IEnumerable<Object> _items; public MockPageEnumerable(IEnumerable<Object> items) { _items = items; } // 实现遍历所需的GetEnumerator方法 public override IEnumerator<Object> GetEnumerator() { return _items.GetEnumerator(); } // 其他抽象方法测试中用不到,直接抛出异常即可 public override Task<Page<Object>> ReadPageAsync(int pageSize, string pageToken, CancellationToken cancellationToken) { throw new NotImplementedException(); } }
修改测试代码,使用这个子类返回预设数据:
[Test] public void TestGetFilesInGCS() { // 构造测试用的GCS对象 var testObjects = new List<Object> { new Object { Name = "document.pdf" }, new Object { Name = "image.png" } }; var storageClientMock = new Mock<StorageClient>(MockBehavior.Loose); // 配置ListObjects返回模拟的PageEnumerable storageClientMock.Setup(m => m.ListObjects(It.IsAny<string>(), It.IsAny<string>())) .Returns(new MockPageEnumerable(testObjects)); var googleService = new GoogleService(storageClientMock.Object); var resultFiles = googleService.GetFilesInGCS("test-bucket", "test-node"); // 验证结果 Assert.Multiple(() => { Assert.That(resultFiles, Is.Not.Empty); Assert.That(resultFiles.Count, Is.EqualTo(2)); Assert.That(resultFiles, Contains.Item("document.pdf")); Assert.That(resultFiles, Contains.Item("image.png")); }); }
方法二:用Moq直接模拟PageEnumerable
如果不想手动编写子类,也可以用Moq直接模拟PageEnumerable<Objects, Object>,重点是模拟GetEnumerator方法:
[Test] public void TestGetFilesInGCS_WithMoq() { var testObjects = new List<Object> { new Object { Name = "data.csv" }, new Object { Name = "archive.zip" } }; // 模拟PageEnumerable对象 var mockPageEnumerable = new Mock<PageEnumerable<Objects, Object>>(); mockPageEnumerable.Setup(m => m.GetEnumerator()) .Returns(testObjects.GetEnumerator()); var storageClientMock = new Mock<StorageClient>(MockBehavior.Loose); storageClientMock.Setup(m => m.ListObjects(It.IsAny<string>(), It.IsAny<string>())) .Returns(mockPageEnumerable.Object); var googleService = new GoogleService(storageClientMock.Object); var resultFiles = googleService.GetFilesInGCS("test-bucket", "test-node"); // 验证结果 Assert.Multiple(() => { Assert.That(resultFiles, Is.Not.Empty); Assert.That(resultFiles.Count, Is.EqualTo(2)); Assert.That(resultFiles, Contains.Item("data.csv")); Assert.That(resultFiles, Contains.Item("archive.zip")); }); }
注意事项
- 如果待测试方法调用的是
ListObjects(string bucket, string prefix)重载(不带ListObjectsOptions),测试中的Setup要对应这个重载,避免匹配不到调用。 - 可以扩展测试用例,覆盖异常场景:让
ListObjects抛出异常,验证日志是否记录、_emailer.SendErrorAsync是否被调用。
内容的提问来源于stack exchange,提问作者Jason Everett
相关产品推荐
相关产品推荐

