7

I am using unit tests to test DocumentDBRepository class. I followed this post as an example for the SQL queries use case. But it shows error of

Message: System.InvalidCastException : Unable to cast object of type 'System.Linq.EnumerableQuery to type 'Microsoft.Azure.Documents.Linq.IDocumentQuery

Here's my code for DocumentDBRepository class

private IDocumentQuery<T> GetQueryBySQL(string queryStr)
{
    var uri = UriFactory.CreateDocumentCollectionUri(_databaseId, _collectionId);
    var feedOptions = new FeedOptions { MaxItemCount = -1, EnableCrossPartitionQuery = true };
    IQueryable<T> filter = _client.CreateDocumentQuery<T>(uri, queryStr, feedOptions); 
    IDocumentQuery<T> query = filter.AsDocumentQuery();
    return query;

}

public async Task<IEnumerable<T>> RunQueryAsync(string queryString)
{
    IDocumentQuery<T> query = GetQueryBySQL(queryString);

    List<T> results = new List<T>();

    while (query.HasMoreResults)
    {
        results.AddRange(await query.ExecuteNextAsync<T>());
    }
    return results;
}

Here's my code for my test class

public async virtual Task Test_GetEntitiesAsyncBySQL()
{
    var id = "100";
    string queryString = "SELECT * FROM c WHERE c.ID = " + id;
    var dataSource = new List<Book> {
            new Book { ID = "100", Title = "abc"}}.AsQueryable();


    Expression<Func<Book, bool>> predicate = t => t.ID == id;
    var expected = dataSource.Where(predicate.Compile());
    var response = new FeedResponse<Book>(expected);

    var mockDocumentQuery = new Mock<DocumentDBRepositoryTest.IFakeDocumentQuery<Book>>();

    mockDocumentQuery
        .SetupSequence(_ => _.HasMoreResults)
        .Returns(true)
        .Returns(false);

    mockDocumentQuery
        .Setup(_ => _.ExecuteNextAsync<Book>(It.IsAny<CancellationToken>()))
        .ReturnsAsync(response);

    var provider = new Mock<IQueryProvider>();
    provider
        .Setup(_ => _.CreateQuery<Book>(It.IsAny<Expression>()))
        .Returns(mockDocumentQuery.Object);

    mockDocumentQuery.As<IQueryable<Book>>().Setup(x => x.Provider).Returns(provider.Object);
    mockDocumentQuery.As<IQueryable<Book>>().Setup(x => x.Expression).Returns(dataSource.Expression);
    mockDocumentQuery.As<IQueryable<Book>>().Setup(x => x.ElementType).Returns(dataSource.ElementType);
    mockDocumentQuery.As<IQueryable<Book>>().Setup(x => x.GetEnumerator()).Returns(() => dataSource.GetEnumerator());

    var client = new Mock<IDocumentClient>();

    client.Setup(_ => _.CreateDocumentQuery<Book>(It.IsAny<Uri>(), It.IsAny<FeedOptions>()))
          .Returns(mockDocumentQuery.Object);

    var documentsRepository = new DocumentDBRepository<Book>(client.Object, "100", "100");

    //Act
    var entities = await documentsRepository.RunQueryAsync(queryString);

    //Assert
    entities.Should()
        .NotBeNullOrEmpty()
        .And.BeEquivalentTo(expected);
}

The break point stops at this line of code:

IQueryable<T> filter = _client.CreateDocumentQuery<T>(uri, queryStr, feedOptions); 

The filter variable shows null exception at a lot of its properties and result view shows empty, when it's supposed to show the expected value that I defined in the test method.

Any clue how to fix it?

Nkosi
  • 235,767
  • 35
  • 427
  • 472
superninja
  • 3,114
  • 7
  • 30
  • 63

3 Answers3

4

The correct CreateDocumentQuery overload needs to be set up on the mocked client.

The method under test uses

IQueryable<T> filter = _client.CreateDocumentQuery<T>(uri, queryStr, feedOptions); 

Yet in arranging the test, the client was set up like

client
    .Setup(_ => _.CreateDocumentQuery<Book>(It.IsAny<Uri>(), It.IsAny<FeedOptions>()))
    .Returns(mockDocumentQuery.Object);

That should be changed to

client
    .Setup(_ => _.CreateDocumentQuery<Book>(It.IsAny<Uri>(), It.IsAny<string>(), It.IsAny<FeedOptions>()))
    .Returns(mockDocumentQuery.Object);

Because of the additional queryStr parameter. It could have also used the string parameter directly as an alternative, given that is is being explicitly injected into the method and can be used as part of the expectation.

client
    .Setup(_ => _.CreateDocumentQuery<Book>(It.IsAny<Uri>(), queryStr, It.IsAny<FeedOptions>()))
    .Returns(mockDocumentQuery.Object);

Since the method under test is not using Linq directly when building the query then there is no need to mock/override the query provider as was in a previous iteration of this topic

Here is the completed test after the above changes

public async virtual Task Test_GetEntitiesAsyncBySQL() {
    //Arrange
    var id = "100";
    string queryString = "SELECT * FROM c WHERE c.ID = " + id;
    var dataSource = new List<Book> {
        new Book { ID = "100", Title = "abc"}
    }.AsQueryable();

    Expression<Func<Book, bool>> predicate = t => t.ID == id;
    var expected = dataSource.Where(predicate.Compile());
    var response = new FeedResponse<Book>(expected);

    var mockDocumentQuery = new Mock<IFakeDocumentQuery<Book>>();

    mockDocumentQuery
        .SetupSequence(_ => _.HasMoreResults)
        .Returns(true)
        .Returns(false);

    mockDocumentQuery
        .Setup(_ => _.ExecuteNextAsync<Book>(It.IsAny<CancellationToken>()))
        .ReturnsAsync(response);

    //Note the change here
    mockDocumentQuery.As<IQueryable<Book>>().Setup(_ => _.Provider).Returns(dataSource.Provider);
    mockDocumentQuery.As<IQueryable<Book>>().Setup(_ => _.Expression).Returns(dataSource.Expression);
    mockDocumentQuery.As<IQueryable<Book>>().Setup(_ => _.ElementType).Returns(dataSource.ElementType);
    mockDocumentQuery.As<IQueryable<Book>>().Setup(_ => _.GetEnumerator()).Returns(() => dataSource.GetEnumerator());

    var client = new Mock<IDocumentClient>();

    //Note the change here
    client
        .Setup(_ => _.CreateDocumentQuery<Book>(It.IsAny<Uri>(), It.IsAny<string>(), It.IsAny<FeedOptions>()))
        .Returns(mockDocumentQuery.Object);

    var documentsRepository = new DocumentDBRepository<Book>(client.Object, "100", "100");

    //Act
    var entities = await documentsRepository.RunQueryAsync(queryString);

    //Assert
    entities.Should()
        .NotBeNullOrEmpty()
        .And.BeEquivalentTo(expected);
}
Nkosi
  • 235,767
  • 35
  • 427
  • 472
1

The reason you see the error seems quite simple to me.

Here is how you set up the parameters list -

client.Setup(_ => _.CreateDocumentQuery<Book>(It.IsAny<Uri>(), It.IsAny<FeedOptions>()))
      .Returns(mockDocumentQuery.Object);

And here is how you call CreateDocumentQuery -

IQueryable<T> filter = _client.CreateDocumentQuery<T>(uri, queryStr, feedOptions);

So basically you've missed queryString. Here is what you should do -

client.Setup(_ => _.CreateDocumentQuery<Book>(It.IsAny<Uri>(), It.IsAny<string>(), It.IsAny<FeedOptions>()))
              .Returns(mockDocumentQuery.Object);
Kiryl
  • 1,416
  • 9
  • 21
1

For some reason the solution suggested by Nkosi doesn't work for me (even tho, logically it seems correct). Please notice the difference in the IQueryProvider mock that interacts with the IEnumerable that we expect as the result of the query.

    // somewhere in your test class
    public interface IFakeDocumentQuery<T> : IDocumentQuery<T>, IOrderedQueryable<T>
    {
    }

    // somewhere in your test method
    var expected = new List<YourType>
    {
        new YourType
        {
            yourField = "yourValue"
        }
    };

    var mockDocumentClient = new Mock<IDocumentClient>();
    var dataSource = expected.AsQueryable();
    var response = new FeedResponse<YourType>(dataSource);
    var mockDocumentQuery = new Mock<IFakeDocumentQuery<YourType>>();

    // the part that gets the work done :)
    var provider = new Mock<IQueryProvider>();
    provider
        .Setup(p => p.CreateQuery<YourType>(It.IsAny<Expression>()))
        .Returns(mockDocumentQuery.Object);
    mockDocumentQuery
        .Setup(q => q.ExecuteNextAsync<YourType>(It.IsAny<CancellationToken>()))
        .ReturnsAsync(response);
    mockDocumentQuery
        .SetupSequence(q => q.HasMoreResults)
        .Returns(true)
        .Returns(false);
    mockDocumentQuery
        .As<IQueryable<YourType>>()
        .Setup(x => x.Provider)
        .Returns(provider.Object);
    mockDocumentQuery
        .As<IQueryable<YourType>>()
        .Setup(x => x.Expression)
        .Returns(dataSource.Expression);
    mockDocumentQuery
        .As<IQueryable<YourType>>()
        .Setup(x => x.ElementType)
        .Returns(dataSource.ElementType);
    mockDocumentQuery
        .As<IQueryable<YourType>>()
        .Setup(x => x.GetEnumerator())
        .Returns(dataSource.GetEnumerator);
    mockDocumentClient
        .Setup(c => c.CreateDocumentQuery<YourType>(It.IsAny<Uri>(), It.IsAny<FeedOptions>()))
        .Returns(mockDocumentQuery.Object);
Nick D
  • 531
  • 4
  • 15