Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I'm writing a data access layer for my application and trying to mock out the CosmosDB SDK dependency for unit testing. I am using NUnit with NSubstitute and have come across the issue where I am trying to mock the return values for Container.GetItemQueryIterator.

I have successfully provided a mock feedIterator as a response for that call and a mock feedResponse as a return value for feedIterator.ReadNextAsync, but I cannot figure out how to inject any sort of values into the FeedResponse to test against

The code I'm trying to test looks like this:

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

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

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

My attempt at mocking out the dependencies like this (Simplified):

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);

Looking at the AzureCosmosDB SDK documentation, there seems to be a FeedResponse constructor for mocking that takes an IEnumerable as a parameter, but NSubstitute complains telling me it can't find this constructor when I attempt to pass in a list to use. Is there an alternative that I can feed some IEnumerable as a FeedResponse? Where am I going wrong?

like image 826
Sin Avatar asked Sep 29 '19 13:09

Sin


3 Answers

I also managed the issue by mocking the GetEnumerator call on the FeedResponse mock. However, something to keep in mind is that if you just set mockFeedIterator.HasMoreResults(true), you'll end up in an infinite loop.

I solved that problem by using Moq's Callback method feature. I configured the HasMoreResults method to return true, and then setup a callback on the ReadNextAsync method to reconfigure HasMoreResults to return false. That way, it will drop into the while loop the first time, populate the return collection based on the mocked GetEnumerator method, and then exit the loop and return that collection to the test method.

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);
like image 88
restlessnomad Avatar answered Oct 23 '22 06:10

restlessnomad


You can mock ReadNextAsync to return a mock of FeedResponse that only has GetEnumerator() defined. The GetEnumerator of the List you create will then be passed through to the underlying implementation of the foreach.

The following example uses Moq, but you should be able to do something similar in your implementation.

var mockFeedResponse = new Mock<FeedResponse<Thing>>();
mockFeedResponse
    .Setup(x => x.GetEnumerator())
    .Returns(
        new List<Thing>
        {
            new Thing(),
            new Thing()
        }.GetEnumerator()
    );
like image 20
Steve Brush Avatar answered Oct 23 '22 06:10

Steve Brush


For what it's worth - I worked around this by changing my code to access the Resource field on the FeedResponse received from CosmosDB. In my tests, I was able to then mock the return value for Resource and get the desired result.

like image 27
Sin Avatar answered Oct 23 '22 07:10

Sin