在使用Linq查询的单元测试中模拟IDocumentQuery [英] Mocking IDocumentQuery in Unit Test that uses Linq queries

查看:68
本文介绍了在使用Linq查询的单元测试中模拟IDocumentQuery的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在编写DocumentDBRepository的单元测试,但是出现了空引用异常.我使用Moq框架和XUnit.

I am writing unit tests for DocumentDBRepository but I got a null reference exception. I use Moq framework and XUnit.

这是我在DocumentDBRepository类中的方法.

public class DocumentDBRepository<T> : IRepository<T> where T: class
{
    private static string DatabaseId;
    private static string CollectionId;
    private static IDocumentClient client;
    public DocumentDBRepository(IDocumentClient documentClient, string databaseId, string collectionId)
    {
        DatabaseId = databaseId;
        CollectionId = collectionId;
        client = documentClient;
        CreateDatabaseIfNotExistsAsync().Wait();
        CreateCollectionIfNotExistsAsync().Wait();
    }

    public async Task<IDocumentQuery<T>> GetQuery(Expression<Func<T, bool>> predicate)
    {
        try
        {
            IDocumentQuery<T> query = client.CreateDocumentQuery<T>(
          UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId),
          new FeedOptions { MaxItemCount = -1, EnableCrossPartitionQuery = true })
          .Where(predicate)
          .AsDocumentQuery();

            return query;
        }
        catch (Exception e) {
            throw;
        }    
    }

    public async Task<IEnumerable<T>> GetEntities(IDocumentQuery<T> query)
    {
        try
        {
            List<T> results = new List<T>();
            while (query.HasMoreResults)
            {
                results.AddRange(await query.ExecuteNextAsync<T>());
            }

            return results;
        }
        catch (Exception e)
        {
            throw;
        }            
    }
}

这是我的测试代码:

public interface IFakeDocumentQuery<T> : IDocumentQuery<T>, IOrderedQueryable<T>
{

}

[Fact]
public async virtual Task Test_GetBooksById()
{

    var expected = new List<Book> {
        new Book { ID = "123", Description = "HarryPotter"},
        new Book { ID = "124", Description = "HarryPotter2"} };


    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 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 query = await documentsRepository.GetQuery(t => t != null);
    var entities = await documentsRepository.GetEntities(query);

    //Assert
    if (entities != null)
    {
        entities.Should().BeEquivalentTo(expected);
    }
}

这是运行测试方法后的错误消息:

Here's the error message after running the test method:

消息:System.NullReferenceException:对象引用未设置为 对象的实例.

Message: System.NullReferenceException : Object reference not set to an instance of an object.

单步执行代码时,错误发生在称为GetQuery()方法的测试代码之后:

When I stepped through the code, the error happens right after the the test code called GetQuery() method:

 IDocumentQuery<T> query = client.CreateDocumentQuery<T>(
              UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId),
              new FeedOptions { MaxItemCount = -1, EnableCrossPartitionQuery = true })
              .Where(predicate)
              .AsDocumentQuery();

这是我的思考过程:单步执行整个代码时,我看不到任何空变量.但是在测试方法第二行的'response'变量中,它确实显示了很多属性为null异常,但结果视图显示了'expected'变量.

Here's my thought process: when I stepped through the entire code, I do not see any null variables. But in the 'response' variable from the second line of the test method, it does show a lot of the properties are null exception but result view shows the 'expected' variable.

我的问题是,是否由于响应变量导致了null引用异常?还是其他地方?

My question is, is it because of the response variable that caused the null reference exception? Or somewhere else?

PS:来自我还尝试将Mock行为设置为严格,并看到此错误消息.

I also tried turning on the Mock behavior to strict and saw this error message.

消息:System.AggregateException:发生一个或多个错误. (IDocumentClient.ReadDatabaseAsync(dbs/123,null)调用失败 具有严格的模拟行为.模拟中的所有调用都必须有一个 相应的设置.) ---- Moq.MockException:IDocumentClient.ReadDatabaseAsync(dbs/123,null)调用因模拟行为严格"而失败.的所有调用 模拟必须具有相应的设置.

Message: System.AggregateException : One or more errors occurred. (IDocumentClient.ReadDatabaseAsync(dbs/123, null) invocation failed with mock behavior Strict. All invocations on the mock must have a corresponding setup.) ---- Moq.MockException : IDocumentClient.ReadDatabaseAsync(dbs/123, null) invocation failed with mock behavior Strict. All invocations on the mock must have a corresponding setup.

推荐答案

怀疑是问题.Where(predicate).我使用提供的示例进行了测试,并删除了.Where子句,该子句执行完毕.

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.

伪接口从IOrderedQueryableIDocumentQuery继承.问题在于,由于List数据源,Where会将其转换回普通的IEnumerable,并且AsDocumentQuery由于期望IDocumentQuery

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

我不喜欢紧密耦合到我无法控制的API.出于这个原因,我将围绕此类实现细节抽象我的方式.

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.

解决方法包括必须提供伪造的Linq IQueryProvider来绕过任何查询并返回从IDocumentQuery派生的类型,以允许AsDocumentQuery正常运行.

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.

但是首先我重构了GetEntities并将GetQuery设为私有,以防止存储库成为泄漏的抽象.

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

请注意,getQuery并未执行任何异步操作,因此无论如何都不应返回Task<>.

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

接下来,在测试中,模拟的IDocumentQuery被设置为允许测试进行到完成.这是通过提供一个模拟的IQueryProvider来实现的,当对Linq查询进行调用时,该模拟的IQueryProvider会返回该模拟的IDocumentQuery. (这是导致问题开始的原因)

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.

这篇关于在使用Linq查询的单元测试中模拟IDocumentQuery的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆