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
相关产品推荐
相关产品推荐

