英文:
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<ICosmosDb> _logger;
/// <summary>
///
/// </summary>
/// <param name="cosmosConfig"></param>
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);
}
/// <summary>Creates a document in the database.</summary>
public async Task CreateItemAsync(Item doc)
{
try
{
await this.RetryUntilNot429Async(async () => await _container.CreateItemAsync<JObject>(JObject.FromObject(doc)));
}
catch (CosmosException cosmosException)
{
_logger.LogError(cosmosException,$"Exception while {nameof(CreateItemAsync)}");
throw;
}
}
/// <summary>
/// Retries the specified method indefinitely until no longer getting a
/// 429 "Too Many Requests" exception after waiting the recommended
/// delay.
/// </summary>
private async Task<T> RetryUntilNot429Async<T>(Func<Task<T>> 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) && (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<Container>();
var mockLogger = new Mock<ILogger<CosmosDb>>();
var mockConfig = new Mock<IOptions<CosmosConfig>>();
var mockCliente = new Mock<CosmosClient>();
mockConfig.Setup(x => x.Value.ContainerId).Returns("containerid");
mockConfig.Setup(x => x.Value.Database).Returns("database");
mockConfig.Setup(x => x.Value.ConnectionString).Returns("conectionstring");
var item = new Item { Id = "123", Action = "update", BatchId = Guid.NewGuid().ToString() };
var itemJObject = JObject.FromObject(item);
var statusCode = System.Net.HttpStatusCode.TooManyRequests;
var cosmosException = new CosmosException("429 Too Many Requests", statusCode, 0, "0", 0);
mockCliente.Setup(x => x.GetContainer(It.IsAny<string>(), It.IsAny<string>())).Returns(mockContainer.Object);
mockContainer.Setup(x => x.CreateItemAsync<JObject>(itemJObject, null, null, default)).ThrowsAsync(cosmosException);
var cosmosDB = new CosmosDb(mockLogger.Object, mockConfig.Object);
// Act
await cosmosDB.CreateItemAsync(item);
// Assert
mockContainer.Verify(x => x.CreateItemAsync<JObject>(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) => {
string endpoint = configuration["EndPointUrl"];
if (string.IsNullOrEmpty(endpoint))
{
throw new ArgumentNullException("Please specify a valid endpoint in the appSettings.json file or your Azure Functions Settings.");
}
string authKey = configuration["AuthorizationKey"];
if (string.IsNullOrEmpty(authKey) || string.Equals(authKey, "Super secret key"))
{
throw new ArgumentException("Please specify a valid AuthorizationKey in the appSettings.json file or your Azure Functions Settings.");
}
CosmosClientBuilder configurationBuilder = new CosmosClientBuilder(endpoint, authKey);
return configurationBuilder
.Build();
});
(source)
Your code can use the injected instance:
public CosmosDb(ILogger<ICosmosDb> 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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论