【问题标题】:Mocking IDocumentQuery in Unit Test that uses SQL Queries在使用 SQL 查询的单元测试中模拟 IDocumentQuery
【发布时间】:2018-04-20 06:59:46
【问题描述】:

我正在使用单元测试来测试DocumentDBRepository 类。我以this post 作为 SQL 查询用例的示例。但它显示了

的错误

消息:System.InvalidCastException:无法转换类型的对象 'System.Linq.EnumerableQuery 输入 'Microsoft.Azure.Documents.Linq.IDocumentQuery

这是我的 DocumentDBRepository 类代码

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

这是我的测试类代码

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

断点停在这行代码:

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

filter 变量在其许多属性中显示空异常,结果视图显示为空,而它应该显示我在测试方法中定义的 expected 值。

有什么办法解决吗?

【问题讨论】:

  • 您必须设置需要查询字符串的CreateDocumentQuery 重载

标签: c# unit-testing azure moq azure-cosmosdb


【解决方案1】:

需要在模拟客户端上设置正确的CreateDocumentQuery 重载。

被测方法使用

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

然而在安排测试时,客户端的设置是这样的

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

应该改成

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

因为额外的queryStr 参数。它也可以直接使用字符串参数作为替代方案,因为它被显式注入到方法中并且可以用作期望的一部分。

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

由于被测方法在构建查询时没有直接使用 Linq,因此无需像本主题的前一次迭代中那样模拟/覆盖查询提供程序

这是上述更改后完成的测试

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

【讨论】:

    【解决方案2】:

    您看到错误的原因对我来说似乎很简单。

    这里是你如何设置参数列表 -

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

    这就是你如何调用 CreateDocumentQuery -

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

    所以基本上你错过了 queryString。这是你应该做的 -

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

    【讨论】:

      【解决方案3】:

      出于某种原因,Nkosi 建议的解决方案对我不起作用(即使如此,在逻辑上它似乎是正确的)。请注意 IQueryProvider 模拟与我们期望作为查询结果的 IEnumerable 交互的差异。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-12-13
        • 2020-02-16
        • 2013-09-05
        • 2017-02-09
        相关资源
        最近更新 更多