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

Mock DbContext执行异步操作时抛出参数表达式无效异常

问题:Mock DbContext异步方法时抛出"Argument expression is not valid"异常

项目中使用了TypedId,参考教程实现DbContext的Mock后,同步场景可正常工作,但在测试仓储异步方法时,DbAsyncQueryProvider的Execute<TResult>方法抛出"Argument expression is not valid"异常;将仓储方法中的AnyAsync改为同步Any并移除await后,测试可正常运行。


待测试的仓储异步方法

public async Task<bool> IsUserAlreadyAddReview(Guid userId, Guid bookId)
{
    return await _dbContext
        .Set<Domain.Entities.Review.Review>()
        .AnyAsync(x => x.BookId == new BookId(bookId) &&
                    x.ReviewCreatedBy == new UserId(userId) &&
                    x.ReviewDeletedOn == null);
}

自定义Mock实现类

DbAsyncEnumerable

using System.Linq.Expressions;
namespace X.Test.BuildingBlocks.Moq;

internal class DbAsyncEnumerable<T> : EnumerableQuery<T>, IAsyncEnumerable<T>, IQueryable<T>
{
    public DbAsyncEnumerable(IEnumerable<T> enumerable) : base(enumerable) { }

    public DbAsyncEnumerable(Expression expression) : base(expression) { }

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

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

DbAsyncEnumerator

namespace X.Test.BuildingBlocks.Moq;

internal class DbAsyncEnumerator<T> : IAsyncEnumerator<T>
{
    private readonly IEnumerator<T> _inner;

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

    public T Current => _inner.Current;

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

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

DbAsyncQueryProvider

using Microsoft.EntityFrameworkCore.Query;
using System.Linq.Expressions;

namespace X.Test.BuildingBlocks.Moq;

internal class DbAsyncQueryProvider<TEntity> : IAsyncQueryProvider
{
    private readonly IQueryProvider _inner;

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

    public IQueryable CreateQuery(Expression expression)
    {
        return new DbAsyncEnumerable<TEntity>(expression);
    }

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

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

    public TResult Execute<TResult>(Expression expression)
    {
        try
        {
            return _inner.Execute<TResult>(expression); // 抛出异常位置
        } catch(Exception ex)
        {
            Console.WriteLine(ex);
        }

        throw new Exception();
    }

    public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken = default)
    {
        return Task.FromResult(Execute<TResult>(expression));
    }

    TResult IAsyncQueryProvider.ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
    {
        return Execute<TResult>(expression);
    }
}

DbContextMock工厂

using Microsoft.EntityFrameworkCore;
using Moq;
using System.Linq.Expressions;

namespace X.Test.BuildingBlocks.Moq;

public class DbContextMock
{
    public static TContext GetMock<TData, TContext>(
        List<TData> mockData,
        Expression<Func<TContext, DbSet<TData>>> dbSetSelectionExpression
    ) where TData : class where TContext : DbContext
    {
        var mockDataQueryable = mockData.AsQueryable();
        var dbSetMock = new Mock<DbSet<TData>>();
        var dbContextMock = new Mock<TContext>();

        dbSetMock
            .As<IAsyncEnumerable<TData>>()
            .Setup(x => x.GetAsyncEnumerator(CancellationToken.None))
            .Returns(new DbAsyncEnumerator<TData>(mockDataQueryable.GetEnumerator()));

        dbSetMock
            .As<IQueryable<TData>>()
            .Setup(x => x.Provider)
            .Returns(new DbAsyncQueryProvider<TData>(mockDataQueryable.Provider));

        dbSetMock
            .As<IQueryable<TData>>()
            .Setup(x => x.Expression)
            .Returns(mockDataQueryable.Expression);

        dbSetMock
            .As<IQueryable<TData>>()
            .Setup(x => x.ElementType)
            .Returns(mockDataQueryable.ElementType);

        dbSetMock
            .As<IQueryable<TData>>()
            .Setup(x => x.GetEnumerator())
            .Returns(mockDataQueryable.GetEnumerator());

        dbSetMock
            .Setup(x => x.Add(It.IsAny<TData>()))
            .Callback<TData>(mockData.Add);

        dbSetMock
            .Setup(x => x.AddRange(It.IsAny<IEnumerable<TData>>()))
            .Callback<IEnumerable<TData>>(mockData.AddRange);

        dbSetMock
            .Setup(x => x.Remove(It.IsAny<TData>()))
            .Callback<TData>(x => mockData.Remove(x));

        dbSetMock
            .Setup(x => x.RemoveRange(It.IsAny<IEnumerable<TData>>()))
            .Callback<IEnumerable<TData>>(x =>
            {
                foreach (var y in x)
                {
                    mockData.Remove(y);
                }
            });

        dbContextMock
            .Setup(dbSetSelectionExpression)
            .Returns(dbSetMock.Object);

        return dbContextMock.Object;
    }
}

测试用例

[Theory]
[AutoData]
public async Task ReviewRepository_WhenIsUserAlreadyAddReview_ThenDataAreCorrect(
    Guid bookIdMock,
    Guid userIdMock
)
{
    // Arrange
    var reviewMock = new List<Revrent.Domain.Entities.Review.Review>
    {
        ReviewMock
            .CreateMock()
            .WithBookId(bookIdMock)
            .WithUserId(userIdMock)
            .Generate(),
        
        ReviewMock
            .CreateMock()
            .WithBookId(bookIdMock)
            .Generate()
    };

    var dbContextMock = DbContextMock.GetMock<Revrent.Domain.Entities.Review.Review, DbContext>(reviewMock, x => x.Set<Revrent.Domain.Entities.Review.Review>());

    var reviewRepository = new X.Persistance.Domains.Review.ReviewRepository(dbContextMock);

    // Act
    var result = await reviewRepository.IsUserAlreadyAddReview(userIdMock, bookIdMock);

    // Assert
    Assert.True(result);
}

内容的提问来源于stack exchange,提问作者Mauty404

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 07:15:25