如何解决测试中IQueryable未实现IAsyncQueryProvider异常问题
问题:单元测试调用EF异步方法时触发System.AggregateException异常
问题说明
编写单元测试验证「按ID获取分类」的方法时,出现System.AggregateException异常,核心原因是内存集合转换的IQueryable未实现EF异步操作所需的IAsyncQueryProvider接口。
相关代码
测试初始化代码
[SetUp] public void Setup() { var config = new MapperConfiguration(cfg => { cfg.AddProfile<MapperInitilizer>(); }); _realMapper = new Mapper(config); _categories = new Mock<IRepository<Category>>(); _unitOfWork = new Mock<IUnitOfWork>(); _unitOfWork.SetupGet(work => work.Categories).Returns(_categories.Object); _logger = new Mock<ILogger<CategoryController>>(); _mapper = new Mock<IMapper>(); _categoryController = new CategoryController(_logger.Object, _mapper.Object,_unitOfWork.Object); category_one = new Category() { Id = 1, CreatedAt = new DateTime(2022, 8,8), Description = "music desc", Title = "music" }; category_two = new Category() { Id = 2, CreatedAt = new DateTime(2022, 9,8), Description = "sport desc", Title = "sport" }; }
CategoryTestRepository的Get方法
public async Task<Category> Get(Expression<Func<Category, bool>> expression, Func<IQueryable<Category>, IIncludableQueryable<Category, object>> include = null) { IQueryable<Category> query = Source.AsQueryable(); return await query.AsNoTracking().FirstOrDefaultAsync(expression); }
按ID获取分类的测试方法
[Test] public void GetCategory_InputCategoryId_ReturnCategoryById() { List<Category> categoryList = new List<Category> {category_one, category_two}; var expectedResult = categoryList[0]; var repo = new CategoryTestRepository(categoryList); var unitOfWork = new Mock<IUnitOfWork>(); unitOfWork.SetupGet(work => work.Categories).Returns(repo); var categoryController = new CategoryController(_logger.Object, _realMapper, unitOfWork.Object); categoryController.GetCategory(1).GetAwaiter().GetResult(); Assert.IsNotEmpty(repo.Source); Assert.AreEqual(expectedResult.Id, repo.Get(x => x.Id == 1).Result); }
错误信息
System.AggregateException : 发生一个或多个错误。(源“IQueryable”的提供程序未实现“IAsyncQueryProvider”。只有实现“IAsyncQueryProvider”的提供程序才能用于 Entity Framework 异步操作。) ----> System.InvalidOperationException : 源“IQueryable”的提供程序未实现“IAsyncQueryProvider”。只有实现“IAsyncQueryProvider”的提供程序才能用于 Entity Framework 异步操作。 在 System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions) 在 System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification) 在 System.Threading.Tasks.Task`1.get_Result()
解决方案
方案1:修改Repository的异步方法实现
List.AsQueryable()生成的EnumerableQuery<T>不支持EF的异步扩展方法,改用同步查询后包装成Task返回:
public async Task<Category> Get(Expression<Func<Category, bool>> expression, Func<IQueryable<Category>, IIncludableQueryable<Category, object>> include = null) { IQueryable<Category> query = Source.AsQueryable(); // 用同步FirstOrDefault查询,再通过Task.FromResult包装为异步返回 return await Task.FromResult(query.AsNoTracking().FirstOrDefault(expression)); }
方案2:使用EF InMemory数据库(推荐)
用EF Core的内存数据库替代普通List,生成的IQueryable自带支持异步操作的IAsyncQueryProvider:
// 初始化内存数据库上下文 var options = new DbContextOptionsBuilder<YourDbContext>() .UseInMemoryDatabase(databaseName: "TestCategoryDb") .Options; using var context = new YourDbContext(options); context.Categories.AddRange(category_one, category_two); context.SaveChanges(); // 基于上下文创建Repository var repo = new CategoryRepository(context);
方案3:优化测试方法的异步调用方式
避免用.GetResult()或.Result阻塞线程,将测试方法改为异步:
[Test] public async Task GetCategory_InputCategoryId_ReturnCategoryById() { List<Category> categoryList = new List<Category> {category_one, category_two}; var expectedResult = categoryList[0]; var repo = new CategoryTestRepository(categoryList); var unitOfWork = new Mock<IUnitOfWork>(); unitOfWork.SetupGet(work => work.Categories).Returns(repo); var categoryController = new CategoryController(_logger.Object, _realMapper, unitOfWork.Object); // 异步调用控制器方法 var result = await categoryController.GetCategory(1); Assert.IsNotEmpty(repo.Source); // 异步获取查询结果 var category = await repo.Get(x => x.Id == 1); Assert.AreEqual(expectedResult.Id, category.Id); }
内容的提问来源于stack exchange,提问作者john otor
相关产品推荐
相关产品推荐

