我在一些地方读到,调用数据库返回 IQueryable 的方法是一个很好的实践,因为它允许使用 Select、Where 等方法在服务中扩展查询,并防止将未过滤的数据加载到内存中。
但是,我在测试使用这种方法的方法时遇到了问题。我收到以下错误:
源 IQueryable 没有实现 IAsyncEnumerable。只有实现 IAsyncEnumerable 的源才能用于实体框架异步操作。
我如何在测试中处理此方法,或者我应该以不同的方式处理它?
这是GenerateCustomerReportAsync方法:
public async Task GenerateCustomerReportAsync()
{
var jurisdictions = await _customerPersistenceService.GetAll()
.Select(j => new { j.Name, j.SurName }).ToListAsync();
}
以及持久化服务中的方法:
public interface ICustomerPersistenceService
{
IQueryable<Customers> GetAll();
}
public IQueryable<Customers> GetAll()
{
return _clientContext.Customers.AsQueryable();
}
这是测试的模拟代码:
var customers = new List<Customer>
{
new Customer { /* initialize customer properties */ }
}.AsQueryable();
subCustomerPersistenceService = Substitute.For<ICustomerPersistenceService>();
subCustomerPersistenceService.GetAll().Returns(customers);
我尝试在测试中使用 .AsQueryable() 作为列表。我希望它能够模拟从数据库返回的 IQueryable 集合的行为,从而使我的方法能够无错误地执行。我正在考虑使用表达式发送选择器,然后返回列表,但我不确定这是否是最好的方法,或者我是否应该只使用 IEnumerable 和 AsEnumerable()。
它一直有效,直到您想要针对
async
执行 IQueryable
操作。 例如,如果您的测试方法看起来像这样:
public async Task<IEnumerable<CustomerViewModel>> GetRecentCustomers(int pageNumber, int pageSize)
{
var cutoffDate = DateTime.Today.AddDays(-7);
var customers = await _customerService.GetAll()
.Where(c => c.Orders.Any(o => o.OrderDate >= cutoffDate)
.ProjectTo<CustomerViewModel>(_config)
.Skip(pageNumber * pageSize)
.Take(pageSize)
.ToListAsync();
return customers;
}
...模拟返回常规
IQueryable
的 List<T>
不起作用,因为底层 List<T>
不支持 async
操作。幸运的是,有一个实现可以将 List<T>
模拟为通过异步调用工作的东西,即 MockQueryable。 (https://www.nuget.org/packages/MockQueryable.Core)那么唯一的变化是:
var customers = new List<Customer>
{
new Customer { /* initialize customer properties */ }
}.AsQueryable();
...您使用:
var customers = new List<Customer>
{
new Customer { /* initialize customer properties */ }
}.BuildMock(); // Returns your IQueryable<Customer>
我建议对所有模拟的 IQueryable 使用 BuildMock,这样,如果您将代码从同步重构为异步,而不破坏现有测试,您的测试将继续工作。