如何模拟 CosmosClient?

huangapple go评论56阅读模式
英文:

How Mock CosmosClient?

问题

public class CosmosDb : ICosmosDb
{
    private readonly CosmosClient _client;
    private readonly CosmosConfig _cosmosConfig;
    private readonly Container _container;
    private readonly ILogger<ICosmosDb> _logger; 

    public CosmosDb(ILogger<ICosmosDb> logger, IOptions<CosmosConfig> cosmosConfig)
    {
        _logger = logger;
        _cosmosConfig = cosmosConfig.Value;
        _client = new CosmosClient(_cosmosConfig.ConnectionString);
        _container = _client.GetContainer(_cosmosConfig.Database, _cosmosConfig.ContainerId);
    }

    public async Task CreateItemAsync(Item doc)
    {
        // Method implementation
    }

    private async Task<T> RetryUntilNot429Async<T>(Func<Task<T>> method)
    {
        // Method implementation
    }
}

public class CosmosDbTest
{
    [Fact]
    public async Task CreateItemAsync_RetrysOn429Error()
    {
        // Test code
    }
}

CosmosDbTest中,您正在尝试创建一个 xUnit 测试以测试CosmosDb类的CreateItemAsync方法,该方法使用CosmosClient构造函数。您的目标是模拟CosmosClient以便进行测试。

英文:

I have the next class that I need to create a xunit test, but I don't know how mock the cosmosclient constructor.

public class CosmosDb : ICosmosDb
    {
        private readonly CosmosClient _client;
        private readonly CosmosConfig _cosmosConfig;
        private readonly Container _container;
        private readonly ILogger&lt;ICosmosDb&gt; _logger; 

        /// &lt;summary&gt;
        /// 
        /// &lt;/summary&gt;
        /// &lt;param name=&quot;cosmosConfig&quot;&gt;&lt;/param&gt;
        public CosmosDb(ILogger&lt;ICosmosDb&gt; logger,IOptions&lt;CosmosConfig&gt; cosmosConfig)
        {
            _logger = logger;
           _cosmosConfig = cosmosConfig.Value;
            _client = new CosmosClient(_cosmosConfig.ConnectionString);
            _container = _client.GetContainer(_cosmosConfig.Database, _cosmosConfig.ContainerId);
        }


        /// &lt;summary&gt;Creates a document in the database.&lt;/summary&gt;
        public async Task CreateItemAsync(Item doc)
        {
            try
            {
                await this.RetryUntilNot429Async(async () =&gt; await _container.CreateItemAsync&lt;JObject&gt;(JObject.FromObject(doc)));
            }
            catch (CosmosException cosmosException)
            {
                _logger.LogError(cosmosException,$&quot;Exception while {nameof(CreateItemAsync)}&quot;);
                throw;
            }
        }

        /// &lt;summary&gt;
        /// Retries the specified method indefinitely until no longer getting a
        /// 429 &quot;Too Many Requests&quot; exception after waiting the recommended
        /// delay.
        /// &lt;/summary&gt;
        private async Task&lt;T&gt; RetryUntilNot429Async&lt;T&gt;(Func&lt;Task&lt;T&gt;&gt; method)
        {
            while (true)
            {
                try
                {
                    return await method();
                }
                catch (CosmosException dce)
                when (dce.StatusCode == (HttpStatusCode)429)
                {
                    await Task.Delay((int)dce.RetryAfter.Value.TotalMilliseconds);
                }
                catch (AggregateException ae)
                when ((ae.InnerException is CosmosException dce) &amp;&amp; (dce.StatusCode == (HttpStatusCode)429))
                {
                    await Task.Delay((int)dce.RetryAfter.Value.TotalMilliseconds);
                }
            }
        }
    }

But when create a xunit test and try to create one using the cosmosclient constructor, it demand a valid url in connection string, and when put one, of course cointainer say don't exist , so how can I mock it?? `

 public class CosmosDbTest
    {
        [Fact]
        public async Task CreateItemAsync_RetrysOn429Error()
        {
            // Arrange
            var mockContainer = new Mock&lt;Container&gt;();
            var mockLogger = new Mock&lt;ILogger&lt;CosmosDb&gt;&gt;();
            var mockConfig = new Mock&lt;IOptions&lt;CosmosConfig&gt;&gt;();
            var mockCliente = new Mock&lt;CosmosClient&gt;();

            mockConfig.Setup(x =&gt; x.Value.ContainerId).Returns(&quot;containerid&quot;);
            mockConfig.Setup(x =&gt; x.Value.Database).Returns(&quot;database&quot;);
            mockConfig.Setup(x =&gt; x.Value.ConnectionString).Returns(&quot;conectionstring&quot;);

            var item = new Item { Id = &quot;123&quot;, Action = &quot;update&quot;, BatchId = Guid.NewGuid().ToString() };
            var itemJObject = JObject.FromObject(item);
            var statusCode = System.Net.HttpStatusCode.TooManyRequests;
            var cosmosException = new CosmosException(&quot;429 Too Many Requests&quot;, statusCode, 0, &quot;0&quot;, 0);

            mockCliente.Setup(x =&gt; x.GetContainer(It.IsAny&lt;string&gt;(), It.IsAny&lt;string&gt;())).Returns(mockContainer.Object);
            mockContainer.Setup(x =&gt; x.CreateItemAsync&lt;JObject&gt;(itemJObject, null, null, default)).ThrowsAsync(cosmosException);
            

            var cosmosDB = new CosmosDb(mockLogger.Object, mockConfig.Object);


            // Act
            await cosmosDB.CreateItemAsync(item);

            // Assert
            mockContainer.Verify(x =&gt; x.CreateItemAsync&lt;JObject&gt;(itemJObject, null, null, default), Times.Exactly(2));
        }

    }

答案1

得分: 1

首先,当在测试项目中能够访问RetryUntilNot429Async时,测试该方法的行为就会变得更容易,而不需要模拟整个 Cosmos DB 部分。

因此,注入 CosmosClient!不要像你当前那样手动创建它,因为现在你不能注入一个模拟的 CosmosClient。要能够注入它,你可以将 CosmosClient 注册为一个 Singleton。

builder.Services.AddSingleton((s) => {
    string endpoint = configuration["EndPointUrl"];
    if (string.IsNullOrEmpty(endpoint))
    {
        throw new ArgumentNullException("请在 appSettings.json 文件或 Azure Functions 设置中指定有效的终结点。");
    }

    string authKey = configuration["AuthorizationKey"];
    if (string.IsNullOrEmpty(authKey) || string.Equals(authKey, "超级秘钥"))
    {
        throw new ArgumentException("请在 appSettings.json 文件或 Azure Functions 设置中指定有效的授权秘钥。");
    }

    CosmosClientBuilder configurationBuilder = new CosmosClientBuilder(endpoint, authKey);
    return configurationBuilder
        .Build();
});

你的代码可以使用注入的实例:

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

然后,为模拟使用无参数构造函数,参见文档

英文:

First of all, when RetryUntilNot429Async is accessible within your test project it is way easier to test the behavior of that method without needing to mock the whole cosmos db stuff.

That said, inject the CosmosClient! Do not create it manually like you do currently as you can now not inject a mocked CosmosClient. To be able to inject it you can register the CosmosClient as a Singleton.

builder.Services.AddSingleton((s) =&gt; {
                string endpoint = configuration[&quot;EndPointUrl&quot;];
                if (string.IsNullOrEmpty(endpoint))
                {
                    throw new ArgumentNullException(&quot;Please specify a valid endpoint in the appSettings.json file or your Azure Functions Settings.&quot;);
                }

                string authKey = configuration[&quot;AuthorizationKey&quot;];
                if (string.IsNullOrEmpty(authKey) || string.Equals(authKey, &quot;Super secret key&quot;))
                {
                    throw new ArgumentException(&quot;Please specify a valid AuthorizationKey in the appSettings.json file or your Azure Functions Settings.&quot;);
                }

                CosmosClientBuilder configurationBuilder = new CosmosClientBuilder(endpoint, authKey);
                return configurationBuilder
                        .Build();
            });

(source)

Your code can use the injected instance:

        public CosmosDb(ILogger&lt;ICosmosDb&gt; logger,CosmosClient client)
        {
            _logger = logger;
            _cosmosConfig = cosmosConfig.Value;
            _client = client;
            _container = _client.GetContainer(_cosmosConfig.Database, _cosmosConfig.ContainerId);
        }

Then use the parameterless constructor for mocking, see the docs

huangapple
  • 本文由 发表于 2023年3月3日 21:33:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/75627735.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定