.NET 6.0与EF Core:如何对含LEFT JOIN的LINQ查询做单元测试?
问题原因
Moq默认模拟的DbSet没有正确处理LINQ GroupJoin(即LEFT JOIN里的into子句)后的空集合场景。当completeInformationList为null时,调用DefaultIfEmpty()会触发ArgumentNullException——因为该方法要求源集合不能为null。而INNER JOIN不需要调用DefaultIfEmpty(),因此不会触发这个问题。
解决方案
不要直接依赖Moq对IQueryable的默认模拟,改用实际的List<T>.AsQueryable()来构建模拟数据源,这样能正确处理分组后的空集合逻辑。
具体实现步骤
- 构造包含测试场景的实体列表:需要包含有匹配订单和无匹配订单的
CostDetails记录,以及对应的OrderInformation记录。 - 基于这些列表模拟
DbSet,让DbSet的IQueryable实现指向列表的查询提供者。 - 模拟
DbContext,返回上述模拟的DbSet实例。
示例代码
// 1. 构造测试数据 var testCostDetails = new List<CostDetails> { new CostDetails { FK_INVTABLEUID = 1, MaximumRetailPrice = 199.99m }, new CostDetails { FK_INVTABLEUID = 2, MaximumRetailPrice = 299.99m }, // 无对应订单的记录 new CostDetails { FK_INVTABLEUID = 3, MaximumRetailPrice = 399.99m } }; var testOrderInfos = new List<OrderInformation> { new OrderInformation { FK_INVTABLEUID = 1, OrderId = "ORD-001" }, new OrderInformation { FK_INVTABLEUID = 3, OrderId = "ORD-003" } }; // 2. 模拟CostDetails的DbSet var mockCostDbSet = new Mock<DbSet<CostDetails>>(); mockCostDbSet.As<IQueryable<CostDetails>>() .Setup(m => m.Provider).Returns(testCostDetails.AsQueryable().Provider); mockCostDbSet.As<IQueryable<CostDetails>>() .Setup(m => m.Expression).Returns(testCostDetails.AsQueryable().Expression); mockCostDbSet.As<IQueryable<CostDetails>>() .Setup(m => m.ElementType).Returns(testCostDetails.AsQueryable().ElementType); mockCostDbSet.As<IQueryable<CostDetails>>() .Setup(m => m.GetEnumerator()).Returns(testCostDetails.GetEnumerator()); // 3. 模拟OrderInformation的DbSet var mockOrderDbSet = new Mock<DbSet<OrderInformation>>(); mockOrderDbSet.As<IQueryable<OrderInformation>>() .Setup(m => m.Provider).Returns(testOrderInfos.AsQueryable().Provider); mockOrderDbSet.As<IQueryable<OrderInformation>>() .Setup(m => m.Expression).Returns(testOrderInfos.AsQueryable().Expression); mockOrderDbSet.As<IQueryable<OrderInformation>>() .Setup(m => m.ElementType).Returns(testOrderInfos.AsQueryable().ElementType); mockOrderDbSet.As<IQueryable<OrderInformation>>() .Setup(m => m.GetEnumerator()).Returns(testOrderInfos.GetEnumerator()); // 4. 模拟DbContext var mockDbContext = new Mock<YourDbContextType>(); mockDbContext.Setup(c => c.CostDetails).Returns(mockCostDbSet.Object); mockDbContext.Setup(c => c.OrderInformation).Returns(mockOrderDbSet.Object);
验证逻辑
用上述模拟的DbContext执行你的LEFT JOIN查询时,completeInformationList会正确返回空集合而非null,调用DefaultIfEmpty()后会生成null的ci对象,最终返回包含所有CostDetails记录的结果集(无匹配订单的记录中ci.OrderId为null)。
内容的提问来源于stack exchange,提问作者shriiihariii
相关产品推荐
相关产品推荐

