如何(我应该)模拟DocumentClb进行DocumentDb单元测试?

Ern*_*sto 18 c# unit-testing moq azure-cosmosdb

从新的CosmosDb模拟器我得到了一个存储库来执行基本的documentdb操作,这个存储库被注入其他类.我想对基本查询进行单元测试.

public class DocumentDBRepository<T> where T : class
{
 //Details ommited...

    public IQueryable<T> GetQueryable()
    {
        return _client.CreateDocumentQuery<T>(
            UriFactory.CreateDocumentCollectionUri(_databaseId, _collectionId),
            new FeedOptions { MaxItemCount = -1, EnableCrossPartitionQuery = true });
    }

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

        return results;
    }


}
Run Code Online (Sandbox Code Playgroud)

此存储库需要一个文档客户端才能工作,它也会在构造函数上注入.

public DocumentDBRepository(string databaseId, IDocumentClient client)
{
    _client = client;
    _databaseId = databaseId;
    _collectionId = GetCollectionName();
}
Run Code Online (Sandbox Code Playgroud)

我最初的方法是使用CosmosDb模拟器,但这需要模拟器运行我不喜欢并使测试更慢.

我的第二种方法是尝试使用文档客户端的模拟.

var data = new List<MyDocumentClass>
{
    new MyDocumentClass{ Description= "BBB" },
    new MyDocumentClass{ Description= "ZZZ" },

}
.AsQueryable()
.OrderBy(q => q.Description);
var client = new Mock<IDocumentClient>();
client.As<IDocumentClient>()
    .Setup(foo => foo.CreateDocumentQuery<MyDocumentClass>(It.IsAny<Uri>(), It.IsAny<FeedOptions>()))
    .Returns(data);

DocumentDBRepository<MyDocumentClass> repo= new DocumentDBRepository<MyDocumentClass>(cosmosDatabase, client.Object);
Run Code Online (Sandbox Code Playgroud)

使用此存储库的代码如下所示:

var query = _documentsRepository.GetQueryable()
                .Where(d => d.Description = description)
                .OrderByDescending(d => d.description)
                .Take(100);
//Execute query async fails. 
var result = await _documentsRepository.ExecuteQueryAsync(query);
Run Code Online (Sandbox Code Playgroud)

它失败是因为存储库尝试将其转换IQueryableIDocumentQuery对象,这对DocumentDb api非常具体(参见ExecuteQueryAsync上面的方法).稍后,它会对它执行HasMoreResults方法.所以问题是,即使我模拟.asDocumentQuery()返回我的对象​​,我也不知道如何提供结果HasMoreResults,ExecuteNextAsync因此它对我的单元测试有意义.

我的第三个选择是直接模拟我的存储库而不是DocumentClient对象.我认为会更简单,但我不会测试很多DocumentDb api.

Nko*_*osi 17

关键是CreateDocumentQuery你正在调用,虽然显示为返回IOrderedQueryable,但封装后的结果也IDocumentQuery将从允许.AsDocumentQuery()工作的内容中派生出来.

现在通常你不应该嘲笑你不拥有的东西.但是,在这种情况下,如果您想要ExecuteQueryAsync完成练习,您可以创建一个假抽象,允许测试完成.

以下示例显示了如何完成它.

[TestClass]
public class DocumentDBRepositoryShould {
    /// <summary>
    /// Fake IOrderedQueryable IDocumentQuery for mocking purposes
    /// </summary>        
    public interface IFakeDocumentQuery<T> : IDocumentQuery<T>, IOrderedQueryable<T> {

    }

    [TestMethod]
    public async Task ExecuteQueryAsync() {
        //Arrange
        var description = "BBB";
        var expected = new List<MyDocumentClass> {
            new MyDocumentClass{ Description = description },
            new MyDocumentClass{ Description = "ZZZ" },
            new MyDocumentClass{ Description = "AAA" },
            new MyDocumentClass{ Description = "CCC" },

        };
        var response = new FeedResponse<MyDocumentClass>(expected);

        var mockDocumentQuery = new Mock<IFakeDocumentQuery<MyDocumentClass>>();
        mockDocumentQuery
            .SetupSequence(_ => _.HasMoreResults)
            .Returns(true)
            .Returns(false);

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

        var client = new Mock<IDocumentClient>();

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

        var cosmosDatabase = string.Empty;

        var documentsRepository = new DocumentDBRepository<MyDocumentClass>(cosmosDatabase, client.Object);

        //Act
        var query = documentsRepository.GetQueryable(); //Simple query.

        var actual = await documentsRepository.ExecuteQueryAsync(query);

        //Assert
        actual.Should().BeEquivalentTo(expected);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • "不嘲笑你不拥有的东西"这句​​话已经脱离了背景.作为单元测试的一部分,您应该使用MOCK,STUB,FAKE或DUMMY来确保正确隔离"被测单元"或"被测系统".这通常意味着您的第三方数据源库.其中一些第三方很容易做到像Cosmos DB这样的另一方非常困难.如果你没有,你就永远无法正确地"单元测试"你自己的存储库或"工作单元"实现,因为它们都将读取和写入数据源,使它们成为"集成测试" (8认同)
  • 不适用于`where`子句或任何Linq子句。 (2认同)