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

如何在xUnit测试中Mock CosmosClient构造函数?

解决CosmosDb类的xUnit测试Mock问题

你的核心问题在于CosmosDb类的构造函数直接硬编码实例化了CosmosClient,导致无法注入Mock对象。要解决这个问题,需要先重构代码以支持依赖注入,再调整测试逻辑。

步骤1:重构CosmosDb类,支持CosmosClient注入

修改构造函数,将CosmosClient作为参数传入,而非在内部实例化:

public class CosmosDb : ICosmosDb
{
    private readonly Container _container;
    private readonly ILogger<ICosmosDb> _logger; 

    public CosmosDb(ILogger<ICosmosDb> logger, IOptions<CosmosConfig> cosmosConfig, CosmosClient client)
    {
        _logger = logger;
        var configValue = cosmosConfig.Value;
        _container = client.GetContainer(configValue.Database, configValue.ContainerId);
    }

    // CreateItemAsync和RetryUntilNot429Async方法保持不变
}

这样修改后,测试时就能传入Mock的CosmosClient,避免连接真实数据库。

步骤2:调整测试代码,正确Mock依赖

原来的测试中,Mock的CosmosClient根本没被CosmosDb类使用,因为构造函数自己new了实例。现在重构后,测试可以正常注入Mock对象,同时要处理重试逻辑的无限循环问题(让第一次调用抛429,第二次成功,才能验证重试次数):

public class CosmosDbTest
{
    [Fact]
    public async Task CreateItemAsync_RetriesOn429Error()
    {
        // 准备Mock对象
        var mockContainer = new Mock<Container>();
        var mockLogger = new Mock<ILogger<ICosmosDb>>();
        var mockConfig = new Mock<IOptions<CosmosConfig>>();
        var mockClient = new Mock<CosmosClient>();

        // 配置CosmosConfig
        mockConfig.Setup(x => x.Value.ContainerId).Returns("containerid");
        mockConfig.Setup(x => x.Value.Database).Returns("database");

        // 测试数据
        var item = new Item { Id = "123", Action = "update", BatchId = Guid.NewGuid().ToString() };
        var itemJObject = JObject.FromObject(item);
        
        // 构造429异常,注意传入有效的RetryAfter时间(避免NullReferenceException)
        var cosmosException = new CosmosException(
            "429 Too Many Requests", 
            HttpStatusCode.TooManyRequests, 
            0, 
            "0", 
            TimeSpan.FromMilliseconds(1)
        );

        // 模拟第一次调用抛429,第二次调用成功
        int callCount = 0;
        mockContainer.Setup(x => x.CreateItemAsync<JObject>(It.IsAny<JObject>(), null, null, default))
            .ReturnsAsync(() =>
            {
                callCount++;
                if (callCount == 1)
                    throw cosmosException;
                return new ItemResponse<JObject>(itemJObject);
            });

        // 关联Mock的CosmosClient和Container
        mockClient.Setup(x => x.GetContainer(It.IsAny<string>(), It.IsAny<string>()))
            .Returns(mockContainer.Object);

        // 实例化待测试的CosmosDb
        var cosmosDB = new CosmosDb(mockLogger.Object, mockConfig.Object, mockClient.Object);

        // 执行测试方法
        await cosmosDB.CreateItemAsync(item);

        // 验证CreateItemAsync被调用了两次(一次失败重试,一次成功)
        mockContainer.Verify(
            x => x.CreateItemAsync<JObject>(It.IsAny<JObject>(), null, null, default), 
            Times.Exactly(2)
        );
    }
}

关键说明

  1. 依赖注入的必要性:硬编码实例化依赖(如new CosmosClient(...))会导致代码无法隔离测试,必须通过构造参数注入依赖才能Mock。
  2. 重试逻辑的测试处理:原代码的RetryUntilNot429Async是无限循环,测试时必须让它在有限次数内成功,否则测试会一直运行直到超时。
  3. CosmosException的构造参数:必须传入有效的RetryAfter时间,否则原代码中dce.RetryAfter.Value会抛出空引用异常。

内容的提问来源于stack exchange,提问作者Julio Améstica

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 22:35:12