如何正确Mock EF Core中的FromSqlInterpolated扩展方法?
错误原因
你遇到的报错是因为FromSqlInterpolated是EF Core提供的静态扩展方法,Moq框架不支持直接对静态扩展方法做Setup/校验,所有静态扩展方法的直接Mock都会触发这个NotSupportedException。
解决方案
方案1:业务层做薄抽象(推荐)
该方案对业务侵入性极低,后续维护成本也更低,只需要把存储过程调用逻辑封装为Repo层的虚方法即可:
首先修改业务代码:
// 你的Repo类中新增虚方法,专门封装存储过程调用逻辑 public virtual List<UserTag> GetUserTagFromSp(string userId) { return this.context.UserTag.FromSqlInterpolated($"ctc_sp_UserTag {userId}").ToList(); } // 原有PrintUserTag方法改为调用上述封装好的虚方法 public List<UserTag> PrintUserTag(string userId) { return GetUserTagFromSp(userId); }
之后测试代码直接Mock这个自定义的虚方法即可,不需要接触EF Core的底层API:
[Fact] public void UserTagTest_ReturnsSuccessResult() { string userId = "7C8FFA01-94FC-42EA-9C43-5CA7FD0400D1"; List<UserTag> userTags = new List<UserTag>() { new UserTag() { Id = userId, UserTagName = "TestName", } }; // 直接Mock你自己定义的Repo方法即可 var mockRepo = new Mock<你的Repo类名>(); mockRepo.Setup(x => x.GetUserTagFromSp(userId)).Returns(userTags); var actual = mockRepo.Object.PrintUserTag(userId); Assert.NotNull(actual); // 其余校验逻辑 }
方案2:Mock底层IQueryProvider(无需修改业务代码)
如果不想调整现有业务代码,可以直接Mock EF Core扩展方法底层依赖的IQueryProvider接口,所有LINQ及EF扩展方法最终都会调用该接口的CreateQuery方法执行逻辑:
测试代码调整如下:
[Fact] public void UserTagTest_ReturnsSuccessResult() { string userId = "7C8FFA01-94FC-42EA-9C43-5CA7FD0400D1"; List<UserTag> userTags = new List<UserTag>() { new UserTag() { Id = userId, UserTagName = "TestName", } }.AsQueryable().ToList(); // 1. Mock IQueryProvider,拦截FromSqlInterpolated的调用 var mockQueryProvider = new Mock<IQueryProvider>(); mockQueryProvider .Setup(p => p.CreateQuery<UserTag>(It.IsAny<MethodCallExpression>())) .Returns(userTags.AsQueryable()); // 2. 构造带Mock Provider的DbSet var mockDbSet = new Mock<DbSet<UserTag>>(); mockDbSet.As<IQueryable<UserTag>>().Setup(m => m.Provider).Returns(mockQueryProvider.Object); mockDbSet.As<IQueryable<UserTag>>().Setup(m => m.Expression).Returns(userTags.AsQueryable().Expression); mockDbSet.As<IQueryable<UserTag>>().Setup(m => m.ElementType).Returns(userTags.AsQueryable().ElementType); mockDbSet.As<IQueryable<UserTag>>().Setup(m => m.GetEnumerator()).Returns(userTags.GetEnumerator()); // 3. 正常Setup Context的DbSet属性 var mockContext = new Mock<你的DbContext类名>(); mockContext.Setup(x => x.UserTag).Returns(mockDbSet.Object); // 4. 后续执行逻辑与校验保持不变 var repo = new 你的Repo类名(mockContext.Object); var actual = repo.PrintUserTag(userId); Assert.NotNull(actual); // 其余校验逻辑 }
如果需要校验传入的SQL语句和参数是否正确,可以调整IQueryProvider的Setup条件,解析MethodCallExpression中传入的FormattableString参数做匹配即可。
内容的提问来源于stack exchange,提问作者Andryano Feumty
相关产品推荐
相关产品推荐

