EF Core 6中Mock DbSet<T>调用AsQueryable返回空列表的原因咨询
你遇到的现象核心原因是:Moq模拟的DbSet
具体细节
AsQueryable()的本质
AsQueryable()是System.Linq.Enumerable的扩展方法,当调用对象本身已经实现IQueryable<T>(比如DbSet),方法会直接返回该对象的 IQueryable<T>实例,而非重新包装。当前模拟逻辑的问题
你的MockDbExtensions通过dbSetMock.As<IQueryable<T>>()设置了IQueryable<T>的Provider、Expression等成员,但配套的TestAsyncEnumerable类重写了IQueryable.Provider属性,返回的是TestAsyncQueryProvider<T>(this),而非使用原始测试数据的Provider。
当调用AsQueryable().ToList()时,Queryable.ToList()会通过这个自定义Provider执行查询:TestAsyncQueryProvider的CreateQuery方法返回TestAsyncEnumerable实例,该实例的Provider又指向自身,形成循环依赖。- 最终执行表达式时,
TestAsyncEnumerable无法正确解析原始测试数据的表达式,导致返回空列表。
显式模拟AsQueryable的作用
如果你直接让AsQueryable()返回原始的测试数据IQueryable<T>(比如testData),相当于绕过了Moq模拟的IQueryable<T>实现,直接使用List原生的IQueryable逻辑,自然能正确返回预期数据。
修复方案
方案1:修正TestAsyncEnumerable的Provider逻辑
移除TestAsyncEnumerable中自定义的IQueryable.Provider重写,让它使用父类EnumerableQuery<T>的原生Provider:
internal class TestAsyncEnumerable<T> : EnumerableQuery<T>, IAsyncEnumerable<T> { public TestAsyncEnumerable(IEnumerable<T> enumerable) : base(enumerable) { } public TestAsyncEnumerable(Expression expression) : base(expression) { } public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default) { return new TestAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator()); } }
方案2:直接让AsQueryable返回原始测试数据
在MockDbSet方法中添加设置,让模拟的DbSet作为IQueryable时,AsQueryable()返回原始测试数据:
public static Mock<DbSet<T>> MockDbSet<T>(this IQueryable<T> testData) where T : class { Mock<DbSet<T>> dbSetMock = new Mock<DbSet<T>>(); // 原有设置... dbSetMock.As<IQueryable<T>>().Setup(m => m.AsQueryable()).Returns(testData); return dbSetMock; }
内容的提问来源于stack exchange,提问作者khachanthanh

