问题描述
从新的 CosmosDb 模拟器我得到了一个存储库来执行基本的 documentdb 操作,这个存储库被注入到其他类中.我想对基本查询进行单元测试.
From the new CosmosDb emulator I got sort of a repository to perform basic documentdb operations, this repository gets injected to other classes. I wanted to unit test a basic query.
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;
}
}
这个存储库需要一个文档客户端才能工作,它也被注入到构造函数中.
This repository needs a document client to work, which also gets injected on the constructor.
public DocumentDBRepository(string databaseId, IDocumentClient client)
{
_client = client;
_databaseId = databaseId;
_collectionId = GetCollectionName();
}
我最初的方法是使用 CosmosDb 模拟器,但这需要运行我不喜欢的模拟器并且使测试变慢.
My initial approach was to use the CosmosDb emulator, but that required the emulator to run which I don't like and makes the tests slower.
我的第二种方法是尝试使用文档客户端的模拟.
My second approach was to try and use a mock of the document client.
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);
使用这个存储库的代码是这样工作的:
The code that uses this repository works like this:
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()
返回我的对象,我也不知道如何为 HasMoreResults
和 ExecuteNextAsync 以便它对我的单元测试有意义.
It fails because the repository tries to convert the IQueryable
to a IDocumentQuery
object, which is very specific to DocumentDb api (See method ExecuteQueryAsync
above). Later on, it executes HasMoreResults
method on it. So the problem is, even if I mock .asDocumentQuery()
to return my object, I don't know how to provide a result for HasMoreResults
and ExecuteNextAsync
so that it makes sense for my unit tests.
我的第三个选择是直接模拟我的存储库而不是 DocumentClient 对象.我认为会更简单,但我不会测试很多 DocumentDb api.
My third option would be to straight mock my repository instead of the DocumentClient object. Would be, I think, simpler, but I wouldn't be testing much of the DocumentDb api.
推荐答案
关键在于您正在调用的 CreateDocumentQuery
,尽管显示为返回 IOrderedQueryable
,封装的结果也将从 IDocumentQuery
派生,这将允许 .AsDocumentQuery()
工作.
The key to this is that the CreateDocumentQuery
you are calling, though shown as returning IOrderedQueryable
, the encapsulated result will also be derived from IDocumentQuery
which is what would allow .AsDocumentQuery()
to work.
现在通常你不应该嘲笑你不拥有的东西.但是,在这种情况下,如果您想将 ExecuteQueryAsync
练习到完成,您可以创建一个虚假的抽象,允许测试执行到完成.
Now normally you should not be mocking what you do not own. However in this case if you want to exercise ExecuteQueryAsync
to completion you can create a fake abstraction that will allow the test to be exercised to completion.
下面的例子展示了如何做到这一点.
The following Example shows how it can be done.
[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);
}
}
这篇关于如何(我应该)模拟 DocumentClient 以进行 DocumentDb 单元测试?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!