You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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异步接口,测试逻辑更贴近真实场景:

  1. 安装NuGet包:Microsoft.EntityFrameworkCore.InMemory
  2. 示例代码:
// 配置内存数据库上下文选项
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>:

  1. 先定义异步枚举相关的包装类:
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 });
    }
}
  1. 配置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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 16:52:40