sup*_*nja 6 unit-testing moq azure asp.net-core azure-cosmosdb
我正在编写单元测试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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
这是我的测试代码:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
这是运行测试方法后的错误消息:
消息:System.NullReferenceException:未将对象引用设置为对象的实例.
当我逐步完成代码时,错误发生在测试代码调用GetQuery()方法之后:
IDocumentQuery<T> query = client.CreateDocumentQuery<T>(
UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId),
new FeedOptions { MaxItemCount = -1, EnableCrossPartitionQuery = true })
.Where(predicate)
.AsDocumentQuery();
Run Code Online (Sandbox Code Playgroud)
这是我的思考过程:当我逐步完成整个代码时,我没有看到任何空变量.但是在测试方法第二行的'response'变量中,它确实显示了很多属性为null异常,但结果视图显示了'expected'变量.
我的问题是,是因为响应变量导致空引用异常?或者别的地方?
PS:从这里测试代码参考
我也尝试将Mock行为打开为严格并看到此错误消息.
消息:System.AggregateException:发生一个或多个错误.(IDocumentClient.ReadDatabaseAsync(dbs/123,null)调用失败,模拟行为为Strict.模拟上的所有调用都必须具有相应的设置.)---- Moq.MockException:IDocumentClient.ReadDatabaseAsync(dbs/123,null)调用失败与模拟行为严格.模拟上的所有调用都必须具有相应的设置.
正如怀疑的问题是.Where(predicate)。我使用提供的示例进行了测试并删除了该.Where子句,它执行完成。
假接口继承自IOrderedQueryable和IDocumentQuery。问题在于,由于数据源的原因,它Where正在将其转换回普通格式,并且由于它期望一个IEnumerableListAsDocumentQueryIDocumentQuery
我不喜欢与我无法控制的 API 紧密耦合。正是出于这个原因,我会围绕这些实现细节抽象出我的方式。
解决方法包括必须提供一个假的 LinqIQueryProvider来绕过任何查询并返回派生自的类型IDocumentQuery,以便允许AsDocumentQuery按预期运行。
但首先我进行了重构GetEntities并设为GetQuery私有,以防止存储库成为一个有漏洞的抽象。
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;
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,getQuery没有执行任何异步操作,因此无论如何它都不应该返回 a Task<>。
IDocumentQuery接下来,在测试中设置模拟以允许测试顺利完成。这是通过提供一个模拟来完成的,当针对它调用 Linq 查询时,IQueryProvider它将返回模拟。(这就是问题的根源)IDocumentQuery
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);
}
Run Code Online (Sandbox Code Playgroud)
这使得测试能够完成、按预期运行并通过测试。
| 归档时间: |
|
| 查看次数: |
1168 次 |
| 最近记录: |