Reputation: 117
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?
Upvotes: 10
Views: 8290
Reputation: 301
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);
Upvotes: 20
Reputation: 3181
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()
);
Upvotes: 4
Reputation: 117
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.
Upvotes: 1