Mocking IDocumentQuery in Unit Test that uses SQL

2019-06-25 02:55发布

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?

2条回答
看我几分像从前
2楼-- · 2019-06-25 03:35

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);
查看更多
狗以群分
3楼-- · 2019-06-25 03:37

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);
}
查看更多
登录 后发表回答