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

时间:2018-01-19 21:16:31

标签: 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;
    }


}

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

public DocumentDBRepository(string databaseId, IDocumentClient client)
{
    _client = client;
    _databaseId = databaseId;
    _collectionId = GetCollectionName();
}

我最初的方法是使用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);

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

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

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

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

2 个答案:

答案 0 :(得分:10)

关键是你正在调用的CreateDocumentQuery,虽然显示为返回IOrderedQueryable,但封装的结果也将来自IDocumentQuery,这将允许{{1}工作。

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

以下示例说明了如何完成。

ExecuteQueryAsync

答案 1 :(得分:2)

这是Nkosi移植到NSubstitute的答案:

[TestClass]
public class DocumentDBRepositoryShould
{
    [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 = Substitute.For<IFakeDocumentQuery<MyDocumentClass>>();

        mockDocumentQuery.HasMoreResults.Returns(true, false);
        mockDocumentQuery.ExecuteNextAsync<MyDocumentClass>(Arg.Any<CancellationToken>())
            .Returns(Task.FromResult(response));
        
        var client = Substitute.For<IDocumentClient>();
        client.CreateDocumentQuery<MyDocumentClass>(Arg.Any<Uri>(), Arg.Any<FeedOptions>())
            .ReturnsForAnyArgs(mockDocumentQuery);
        var cosmosDatabase = string.Empty;
        var documentsRepository = new DocumentDBRepository<MyDocumentClass>(cosmosDatabase, client);
        
        //Act
        var actual = await documentsRepository.GetDataAsync(); //Simple query.

        //Assert
        actual.Should().BeEquivalentTo(expected);
    }

    public class MyDocumentClass
    {
        public string Description { get; set; }
    }
    
    public interface IFakeDocumentQuery<T> : IDocumentQuery<T>, IOrderedQueryable<T> {

    }
    
    public class DocumentDBRepository<T>
    {
        private readonly string cosmosDatabase;
        private readonly IDocumentClient documentClient;

        public DocumentDBRepository(string cosmosDatabase, IDocumentClient documentClient)
        {
            this.cosmosDatabase = cosmosDatabase;
            this.documentClient = documentClient;
        }

        public async Task<IEnumerable<MyDocumentClass>> GetDataAsync()
        {
            var documentUri = UriFactory.CreateDocumentCollectionUri(cosmosDatabase, "test-collection");
        
            var query = documentClient
                .CreateDocumentQuery<MyDocumentClass>(documentUri)
                .AsDocumentQuery();
        
            var list = new List<MyDocumentClass>();
            while (query.HasMoreResults)
            {
                var rules = await query.ExecuteNextAsync<MyDocumentClass>();
                list.AddRange(rules);
            }
            return list;
        }
    }
}