在使用Linq查询的单元测试中模拟IDocumentQuery [英] Mocking IDocumentQuery in Unit Test that uses Linq queries
问题描述
我正在编写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.
伪接口从IOrderedQueryable
和IDocumentQuery
继承.问题在于,由于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屋!