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

使用xUnit与Moq测试FindAsync方法时遭遇IAsyncQueryProvider实现异常

问题描述

使用xUnit和Moq对ParticipantService的FindAsync方法做单元测试时,抛出以下异常:

Message: 
System.Exception : The provider for the source 'IQueryable' doesn't implement 'IAsyncQueryProvider'. Only providers that implement 'IAsyncQueryProvider' can be used for Entity Framework asynchronous operations.

测试代码:

[Fact]
public async Task FindAsyncTestAsync()
{
    var participantList = ParticipantMockData.ListAllAsyncEntity(); // 非空,包含2个元素

   _participantRepository.Setup(_ => _.FindAsync(new Guid("ffbf486f-bf31-4bb4-8fc2-5beaae59e127")))
         .Returns(Task.FromResult(participantList[0]));

   var sut = new ParticipantService(
     _participantRepository.Object,
     _participantSupportNeed.Object,
     _participantConnection.Object,
     _participantDisability.Object,
     _participantMedicalCondition.Object,
     _supportNeed.Object,
     _disabilityn.Object,
     _medicalCodition.Object,
     _participantApplicationUser.Object,
     _appLogger.Object);

   var result = await sut.FindAsync(new Guid("ffbf486f-bf31-4bb4-8fc2-5beaae59e127"));

   Assert.NotNull(result);

   Assert.Equal(participantList[0].Id, result?.Id);
 }

IRepository接口定义:

public interface IRepository<E> where E : IEntityBase
{
    Task<List<E>> ListAsync(bool withInactives = false);
    IQueryable<E> AsNoTracking(bool withInactives = false);
    Task<E?> FindAsync(params object[] keyValues);
}

ParticipantService的FindAsync实现:

public async Task<ParticipantAsyncModel?> FindAsync(Guid id)
{
    try
    {
        var participant = await participantRepository.AsNoTracking()
        .Where(a => a.Id == id)
        .Include(a => a.ParticipantAddresses)
        .Include(a => a.ParticipantConnections)!.ThenInclude(a => a.Connection)
        .Include(a => a.ParticipantApplicationUsers)!.ThenInclude(a => a.ApplicationUser)
        .FirstOrDefaultAsync();

        var participantView =
             new ParticipantAsyncModel()
             {
                // 省略对象初始化代码
             };

        if (participantView is null) return null;

        var participantModel = participantView.Adapt<ParticipantAsyncModel>();
        return participantModel;
    }
    catch (Exception ex)
    {
        throw new Exception(ex.Message);
    }
}
问题原因

ParticipantService的FindAsync方法并未调用仓储的FindAsync方法,而是调用AsNoTracking()获取IQueryable集合,之后使用EF Core的异步扩展方法FirstOrDefaultAsync查询。

普通内存集合(如List)转成的IQueryable,其QueryProvider未实现IAsyncQueryProvider接口,无法支持EF Core的异步查询操作,因此抛出异常。而测试中Mock的FindAsync方法根本没有被执行,完全无效。

解决办法

1. 实现支持异步查询的IQueryable扩展

手动编写工具类,将普通IEnumerable转成支持异步查询的IQueryable:

public static class AsyncQueryableExtensions
{
    public static IQueryable<T> AsAsyncQueryable<T>(this IEnumerable<T> source)
    {
        return new AsyncQueryable<T>(source.AsQueryable());
    }
}

public class AsyncQueryable<T> : EnumerableQuery<T>, IAsyncEnumerable<T>, IQueryable<T>
{
    public AsyncQueryable(IEnumerable<T> enumerable) : base(enumerable) { }
    public AsyncQueryable(Expression expression) : base(expression) { }

    public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default)
    {
        return new AsyncEnumerator<T>(this.AsEnumerable().GetEnumerator());
    }

    IQueryProvider IQueryable.Provider => new AsyncQueryProvider<T>(this.Provider);
}

public class AsyncQueryProvider<TEntity> : IAsyncQueryProvider
{
    private readonly IQueryProvider _inner;

    public AsyncQueryProvider(IQueryProvider inner)
    {
        _inner = inner;
    }

    public IQueryable CreateQuery(Expression expression)
    {
        return new AsyncQueryable<object>(expression);
    }

    public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
    {
        return new AsyncQueryable<TElement>(expression);
    }

    public object Execute(Expression expression)
    {
        return _inner.Execute(expression);
    }

    public TResult Execute<TResult>(Expression expression)
    {
        return _inner.Execute<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);

        return (TResult)typeof(Task).GetMethod(nameof(Task.FromResult))!
            .MakeGenericMethod(resultType)
            .Invoke(null, new[] { executeMethod.Invoke(this, new[] { expression }) })!;
    }
}

public class AsyncEnumerator<T> : IAsyncEnumerator<T>
{
    private readonly IEnumerator<T> _inner;

    public AsyncEnumerator(IEnumerator<T> inner)
    {
        _inner = inner;
    }

    public T Current => _inner.Current;

    public ValueTask DisposeAsync()
    {
        _inner.Dispose();
        return ValueTask.CompletedTask;
    }

    public ValueTask<bool> MoveNextAsync()
    {
        return ValueTask.FromResult(_inner.MoveNext());
    }
}

2. 修改测试代码

删除对FindAsync的Mock,转而MockAsNoTracking方法,返回支持异步查询的IQueryable集合:

[Fact]
public async Task FindAsyncTestAsync()
{
    var participantList = ParticipantMockData.ListAllAsyncEntity(); 
    // 将普通集合转为支持异步查询的IQueryable
    var asyncQueryable = participantList.AsAsyncQueryable();

    // Mock AsNoTracking方法,返回准备好的异步查询集合
    _participantRepository.Setup(_ => _.AsNoTracking(It.IsAny<bool>()))
        .Returns(asyncQueryable);

    var sut = new ParticipantService(
      _participantRepository.Object,
      _participantSupportNeed.Object,
      _participantConnection.Object,
      _participantDisability.Object,
      _participantMedicalCondition.Object,
      _supportNeed.Object,
      _disabilityn.Object,
      _medicalCodition.Object,
      _participantApplicationUser.Object,
      _appLogger.Object);

    var targetId = new Guid("ffbf486f-bf31-4bb4-8fc2-5beaae59e127");
    var result = await sut.FindAsync(targetId);

    Assert.NotNull(result);
    Assert.Equal(participantList[0].Id, result?.Id);
}

3. 可选:使用第三方库简化实现

如果不想手动编写异步查询工具类,可以安装EntityFrameworkCore.Testing.MoqNuGet包,它提供了开箱即用的Mock EF Core异步查询能力:

// 安装包后,直接构建支持异步的Mock集合
var asyncQueryable = participantList.AsQueryable().BuildMockDbSet().AsQueryable();
// Mock AsNoTracking方法
_participantRepository.Setup(_ => _.AsNoTracking(It.IsAny<bool>())).Returns(asyncQueryable);

内容的提问来源于stack exchange,提问作者hanushi-thana

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 21:15:13