如何在 CosmosSDK v3+ 的 FeedResponse 中提供模拟值?

How to provide mock values in a FeedResponse for CosmosSDK v3+?

我正在为我的应用程序编写数据访问层,并尝试模拟 CosmosDB SDK 依赖项以进行单元测试。我将 NUnit 与 NSubstitute 一起使用,遇到了我试图模拟 Container.GetItemQueryIterator 的 return 值的问题。

我已成功提供模拟 feedIterator 作为该调用的响应,并提供模拟 feedResponse 作为 feedIterator.ReadNextAsync 的 return 值,但我不知道如何将任何类型的值注入要测试的 FeedResponse

我要测试的代码如下所示:

var feedIterator = container.GetItemQueryIterator<T>(queryDefinition);

  while (feedIterator.HasMoreResults){
    result.success = true;

    foreach (var item in await feedIterator.ReadNextAsync()){
      list.Add(item);
    }
  }

我尝试模拟这样的依赖关系(简化):

this.mockFeedResponse = Substitute.For<FeedResponse<T>>(this.mockApplicationList);         
this.mockFeedIterator = Substitute.For<FeedIterator<T>>();
this.mockFeedIterator.ReadNextAsync().ReturnsForAnyArgs(Task.FromResult(this.mockFeedResponse));
this.mockFeedIterator.HasMoreResults.Returns(true);

查看 AzureCosmosDB SDK 文档,似乎有一个用于模拟的 FeedResponse 构造函数,它将 IEnumerable 作为参数,但是 NSubstitute 抱怨说当我尝试将列表传递给采用。是否有替代方法可以将一些 IEnumerable 作为 FeedResponse 提供?我哪里错了?

物有所值 - 我通过更改代码以访问从 CosmosDB 收到的 FeedResponse 上的 Resource 字段来解决此问题。在我的测试中,我能够模拟 Resource 的 return 值并获得所需的结果。

您可以模拟 ReadNextAsync 到 return 只定义了 GetEnumerator()FeedResponse 的模拟。然后,您创建的 ListGetEnumerator 将传递给 foreach.

的底层实现

以下示例使用 Moq,但您应该能够在您的实现中执行类似的操作。

var mockFeedResponse = new Mock<FeedResponse<Thing>>();
mockFeedResponse
    .Setup(x => x.GetEnumerator())
    .Returns(
        new List<Thing>
        {
            new Thing(),
            new Thing()
        }.GetEnumerator()
    );

我还通过在 FeedResponse 模拟上模拟 GetEnumerator 调用来解决这个问题。但是,要记住的一点是,如果您只设置 mockFeedIterator.HasMoreResults(true),您将陷入无限循环。

我使用 Moq 的 Callback 方法功能解决了这个问题。我将 HasMoreResults 方法配置为 return true,然后在 ReadNextAsync 方法上设置回调以将 HasMoreResults 重新配置为 return false。这样,它将第一次进入 while 循环,根据模拟的 GetEnumerator 方法填充 return 集合,然后退出循环并 return 该集合到测试方法。

var myItems = new List<MyItem>
{
    new MyItem(),
    new MyItem()
};

var feedResponseMock = new Mock<FeedResponse<MyItem>>();
feedResponseMock.Setup(x => x.GetEnumerator()).Returns(myItems.GetEnumerator());

var feedIteratorMock = new Mock<FeedIterator<MyItem>>();
feedIteratorMock.Setup(f => f.HasMoreResults).Returns(true);
feedIteratorMock
    .Setup(f => f.ReadNextAsync(It.IsAny<CancellationToken>()))
    .ReturnsAsync(feedResponseMock.Object)
    .Callback(() => feedIteratorMock
        .Setup(f => f.HasMoreResults)
        .Returns(false));

var containerMock = new Mock<Container>();
containerMock
    .Setup(c => c.GetItemQueryIterator<MyItem>(
        It.IsAny<QueryDefinition>(),
        It.IsAny<string>(),
        It.IsAny<QueryRequestOptions>()))
    .Returns(feedIteratorMock.Object);