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

如何用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 11:23:16