Mocking IDocumentQuery in Unit Test that uses Linq queries

百般思念 提交于 2019-11-28 11:28:57

As suspected the problem is .Where(predicate). I ran a test with the provided example and removed the .Where clause and it executed to completion.

The fake interface inherits from both IOrderedQueryable and IDocumentQuery. The issue is that the Where is converting it back to a plain IEnumerable because of the List data source and the AsDocumentQuery is crapping out as it is expecting an IDocumentQuery

I am not a fan of tightly coupling to APIs I can't control. I would abstract my way around such implementation details for that very reason.

The work around involved having to provide a fake Linq IQueryProvider to bypass any queries and return a type that derives from IDocumentQuery so as to allow AsDocumentQuery to behave as intended.

But first I refactored GetEntities and made GetQuery private to stop the repository from being a leaky abstraction.

private IDocumentQuery<T> getQuery(Expression<Func<T, bool>> predicate) {
    var uri = UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId);
    var feedOptions = new FeedOptions { MaxItemCount = -1, EnableCrossPartitionQuery = true };
    var queryable = client.CreateDocumentQuery<T>(uri, feedOptions);
    IQueryable<T> filter = queryable.Where(predicate);
    IDocumentQuery<T> query = filter.AsDocumentQuery();
    return query;
}

public async Task<IEnumerable<T>> GetEntities(Expression<Func<T, bool>> predicate) {
    try {
        IDocumentQuery<T> query = getQuery(predicate);
        var results = new List<T>();
        while (query.HasMoreResults) {
            results.AddRange(await query.ExecuteNextAsync<T>());
        }
        return results;
    } catch (Exception e) {
        throw;
    }
}

Note that getQuery is not doing anything async so it should not be returning a Task<> anyway.

Next in the test the mocked IDocumentQuery was set up to allow the test to flow to completion. This was done by providing a mocked IQueryProvider the would return the mocked IDocumentQuery when Linq queries are invoked against it. (which was the cause of the problem to begin with)

public async virtual Task Test_GetBooksById() {
    //Arrange
    var id = "123";
    Expression<Func<Book, bool>> predicate = t => t.ID == id;
    var dataSource = new List<Book> {
        new Book { ID = id, Description = "HarryPotter"},
        new Book { ID = "124", Description = "HarryPotter2"} 
    }.AsQueryable();

    var expected = dataSource.Where(predicate);

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

    var provider = new Mock<IQueryProvider>();
    provider
        .Setup(_ => _.CreateQuery<Book>(It.IsAny<System.Linq.Expressions.Expression>()))
        .Returns((Expression expression) => {                
            if (expression != null) {
                dataSource = dataSource.Provider.CreateQuery<Book>(expression);
            }
            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, "123", "123");

    //Act
    var entities = await documentsRepository.GetEntities(predicate);

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

This allowed the test to be exercised to completion, behave as expected, and pass the test.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!