我正在为 DocumentDBRepository
编写单元测试,但出现空引用异常。我使用 Moq 框架和 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);
}
}
这是运行测试方法后的错误消息:
Message: System.NullReferenceException : Object reference not set to an instance of an object.
当我单步执行代码时,错误发生在调用 GetQuery()
方法的测试代码之后:
IDocumentQuery<T> query = client.CreateDocumentQuery<T>(
UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId),
new FeedOptions { MaxItemCount = -1, EnableCrossPartitionQuery = true })
.Where(predicate)
.AsDocumentQuery();
这是我的思考过程:当我单步执行整个代码时,我没有看到任何空变量。但是在测试方法第二行的“response”变量中,它确实显示了很多属性为空的异常,但结果 View 显示了“预期”变量。
我的问题是,是否是因为响应变量导致了空引用异常?还是其他地方?
PS:测试代码引用来自here
我还尝试将 Mock 行为打开为严格,并看到了此错误消息。
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
条款并执行至完成。
假接口(interface)继承自 IOrderedQueryable
和IDocumentQuery
。问题是Where
正在将其转换回普通的 IEnumerable
因为List
数据源和 AsDocumentQuery
正在崩溃,因为它期待 IDocumentQuery
我不喜欢与我无法控制的 API 紧密耦合。正是出于这个原因,我会围绕此类实现细节进行抽象。
解决方法包括必须提供一个假的 Linq IQueryProvider
绕过任何查询并返回派生自 IDocumentQuery
的类型以便允许 AsDocumentQuery
按预期行事。
但首先我重构了GetEntities
并制作GetQuery
private 来阻止存储库成为一个有漏洞的抽象。
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<>
无论如何。
下一步在测试中被 mock IDocumentQuery
被设置为允许测试顺利完成。这是通过提供模拟的 IQueryProvider
来完成的将返回 mock 的 IDocumentQuery
当针对它调用 Linq 查询时。 (这就是问题开始的原因)
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);
}
这使得测试能够完成、按预期运行并通过测试。
关于unit-testing - 在使用 Linq 查询的单元测试中模拟 IDocumentQuery,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49906029/