使用Moq模拟DbSet.Where(lambda)时单元测试失败的排查
Include方法未处理导致查询返回null 看起来你踩了EF Core/EF6单元测试中Mock DbSet的一个常见坑——你的Mock没有处理Include方法的链式调用,这才是导致查询返回null的核心原因。让我一步步帮你理清问题并解决:
问题根源解释
你的待测试方法里调用了三次Include,而当前的GetQueryableMockDbSet只模拟了基础的IQueryable接口方法(Provider、Expression这些),完全没处理Include扩展方法。当代码执行Include后,会返回一个新的IIncludableQueryable实例,这个实例并没有和你模拟的数据源绑定,后续的Where和FirstOrDefault其实是在一个空的查询链上执行,自然找不到匹配的数据。
而你移除Where条件后能返回第一个元素,是因为此时FirstOrDefault直接作用在最初Mock的DbSet上(虽然Include依然无效,但没有过滤条件,所以能拿到模拟数据的第一个元素)。
解决方案步骤
1. 扩展Mock DbSet以支持Include方法
修改你的GetQueryableMockDbSet方法,添加对Include方法的Setup,让它返回自身,保证整个查询链都绑定到你的模拟数据源上:
using Microsoft.EntityFrameworkCore.Query; // EF Core需要的命名空间,EF6请替换为对应命名空间 using Moq; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using Microsoft.EntityFrameworkCore; public DbSet<T> GetQueryableMockDbSet<T>(List<T> sourceList) where T : class { var queryable = sourceList.AsQueryable(); var dbSet = new Mock<DbSet<T>>(); // 基础IQueryable接口模拟 dbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryable.Provider); dbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression); dbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType); dbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(() => queryable.GetEnumerator()); // 关键:模拟Include方法,返回自身以维持查询链 dbSet.As<IIncludableQueryable<T, object>>() .Setup(m => m.Include(It.IsAny<Expression<Func<T, object>>>())) .Returns(dbSet.Object); // 支持多级Include(可选,如果你有ThenInclude的需求) dbSet.As<IIncludableQueryable<T, object>>() .Setup(m => m.ThenInclude(It.IsAny<Expression<Func<object, object>>>())) .Returns(dbSet.Object); // 原有Add/Remove操作的模拟 dbSet.Setup(m => m.Add(It.IsAny<T>())).Callback<T>(sourceList.Add); dbSet.Setup(m => m.Remove(It.IsAny<T>())).Callback<T>(sourceList.Remove); return dbSet.Object; }
2. 确保模拟数据包含匹配的记录
检查你的GenerateDbSetAusweise方法生成的列表中,是否存在Kennzeichen属性等于测试参数"k-fz 4711"的Ausweis实例。如果没有,即使Mock正确,也会返回null。比如可以手动添加一条匹配的测试数据:
public List<Ausweis> GenerateDbSetAusweise() { return new List<Ausweis> { new Ausweis { Kennzeichen = "k-fz 4711", Organisation = new Organisation(), // 确保关联对象也被初始化,避免空引用 Ersteller = new User(), Inhaber = new User() }, // 其他模拟数据... }; }
3. 验证查询条件的精确性
确认待测试方法中的Where条件是精确匹配:x.Kennzeichen == kennzeichen。如果模拟数据中的Kennzeichen存在大小写、空格差异,也会导致匹配失败。如果业务需求允许忽略大小写,可以将条件改为:
.Where(x => x.Kennzeichen.Equals(kennzeichen, StringComparison.OrdinalIgnoreCase))
测试验证
修改完成后重新运行测试,此时Include方法会正确返回Mock的DbSet,后续的Where和FirstOrDefault能正常查询模拟数据源,应该就能得到预期的Ausweis实例了。
内容的提问来源于stack exchange,提问作者Mike F.

