C# & EF Core 8中无法Mock异步方法的问题求助
解决EF Core异步仓储测试的IAsyncEnumerable错误
问题根源
你参考的微软文档是针对Entity Framework 6的,而.NET 8适配的是Entity Framework Core 8,两者异步查询的实现机制完全不同。EF6的AsyncQueryable扩展逻辑不适用于EF Core,导致你Mock出的IQueryable对象没有实现EF Core异步操作要求的IAsyncEnumerable<T>接口,最终抛出错误。
解决方案
方法1:使用EF Core官方内存数据库测试(推荐)
EF Core提供了专门的内存数据库包,无需手动Mock异步接口,测试逻辑更贴近真实场景:
- 安装NuGet包:
Microsoft.EntityFrameworkCore.InMemory - 示例代码:
// 配置内存数据库上下文选项 var options = new DbContextOptionsBuilder<YourDbContext>() .UseInMemoryDatabase(databaseName: "Test_Blog_Db") .Options; // 初始化测试数据 using var context = new YourDbContext(options); context.Blogs.AddRange(new List<Blog> { new Blog { Id = 1, Name = "测试博客1" }, new Blog { Id = 2, Name = "测试博客2" } }); context.SaveChanges(); // 注入上下文到仓储/服务 var blogService = new BlogService(context); // 建议用await替代.Result,避免死锁风险 var blogs = await blogService.GetAllBlogsAsync();
方法2:手动实现IAsyncEnumerable Mock
如果必须Mock DbContext而非使用内存数据库,需要让Mock的DbSet同时实现IQueryable<T>和IAsyncEnumerable<T>:
- 先定义异步枚举相关的包装类:
public class AsyncEnumerable<T> : EnumerableQuery<T>, IAsyncEnumerable<T>, IQueryable<T> { public AsyncEnumerable(IEnumerable<T> enumerable) : base(enumerable) { } public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default) { return new AsyncEnumerator<T>(this.AsEnumerable().GetEnumerator()); } IQueryProvider IQueryable.Provider => new AsyncQueryProvider<T>(this.Provider); } public class AsyncEnumerator<T> : IAsyncEnumerator<T> { private readonly IEnumerator<T> _enumerator; public AsyncEnumerator(IEnumerator<T> enumerator) => _enumerator = enumerator; public ValueTask DisposeAsync() { _enumerator.Dispose(); return ValueTask.CompletedTask; } public ValueTask<bool> MoveNextAsync() { return ValueTask.FromResult(_enumerator.MoveNext()); } public T Current => _enumerator.Current; } public class AsyncQueryProvider<T> : IAsyncQueryProvider { private readonly IQueryProvider _innerProvider; public AsyncQueryProvider(IQueryProvider innerProvider) => _innerProvider = innerProvider; public IQueryable CreateQuery(Expression expression) => new AsyncEnumerable<T>(_innerProvider.CreateQuery<T>(expression)); public IQueryable<TElement> CreateQuery<TElement>(Expression expression) => new AsyncEnumerable<TElement>(_innerProvider.CreateQuery<TElement>(expression)); public object Execute(Expression expression) => _innerProvider.Execute(expression); public TResult Execute<TResult>(Expression expression) => _innerProvider.Execute<TResult>(expression); public IAsyncEnumerable<TResult> ExecuteAsync<TResult>(Expression expression) => new AsyncEnumerable<TResult>(_innerProvider.Execute<IEnumerable<TResult>>(expression)); public TResult ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken) { var resultType = typeof(TResult).GetGenericArguments()[0]; var executeMethod = typeof(IQueryProvider).GetMethod( nameof(IQueryProvider.Execute), new[] { typeof(Expression) })! .MakeGenericMethod(resultType); var enumerator = (IEnumerable)executeMethod.Invoke(_innerProvider, new[] { expression })!; return (TResult)typeof(AsyncEnumerable<>) .MakeGenericType(resultType) .GetConstructor(new[] { typeof(IEnumerable) })! .Invoke(new[] { enumerator }); } }
- 配置Mock的DbSet:
// 构造测试数据 var testBlogs = new List<Blog> { new Blog { Id = 1, Name = "测试博客" } }.AsQueryable(); // Mock DbSet var mockDbSet = new Mock<DbSet<Blog>>(); // 实现IQueryable接口 mockDbSet.As<IQueryable<Blog>>().Setup(m => m.Provider).Returns(new AsyncQueryProvider<Blog>(testBlogs.Provider)); mockDbSet.As<IQueryable<Blog>>().Setup(m => m.Expression).Returns(testBlogs.Expression); mockDbSet.As<IQueryable<Blog>>().Setup(m => m.ElementType).Returns(testBlogs.ElementType); mockDbSet.As<IQueryable<Blog>>().Setup(m => m.GetEnumerator()).Returns(testBlogs.GetEnumerator()); // 实现IAsyncEnumerable接口 mockDbSet.As<IAsyncEnumerable<Blog>>().Setup(m => m.GetAsyncEnumerator(It.IsAny<CancellationToken>())) .Returns(new AsyncEnumerator<Blog>(testBlogs.GetEnumerator())); // Mock DbContext var mockContext = new Mock<YourDbContext>(); mockContext.Setup(c => c.Blogs).Returns(mockDbSet.Object); // 初始化服务并调用方法 var blogService = new BlogService(mockContext.Object); var blogs = await blogService.GetAllBlogsAsync();
额外注意
- 不要用
.Result调用异步方法,容易引发线程死锁,统一使用await关键字。
内容的提问来源于stack exchange,提问作者David Zhu
相关产品推荐
相关产品推荐

