调用两次DbContext获取Moq模拟数据时数据消失问题排查
嘿,我帮你找到问题所在了!你遇到的第二次调用ToList()数据消失的情况,核心是Mock DbSet时枚举器的设置方式不对,和Moq的Returns机制直接相关。
问题根源:枚举器被复用导致二次遍历无数据
你的GetMockSet方法里,对GetEnumerator的Setup是这么写的:
mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(querable.GetEnumerator());
这里的问题在于:querable.GetEnumerator()会在Setup阶段就创建一个枚举器实例,Moq会把这个实例缓存起来,后续所有调用GetEnumerator()都会返回同一个对象。当你第一次调用ToList()时,这个枚举器会被完整遍历到末尾;第二次再调用ToList()时,你拿到的还是那个已经耗尽的枚举器,自然读不到任何数据。
Moq的Returns机制差异
这里得明确Moq中Returns的两种核心用法,这是解决问题的关键:
- 直接传值(如
Returns(querable.GetEnumerator())):值会在Setup时就计算完成并缓存,后续所有调用都返回同一个实例。 - 传入lambda表达式(如
Returns(() => querable.GetEnumerator())):每次调用目标方法时,都会执行lambda表达式,返回一个全新的实例。
你的场景需要每次调用GetEnumerator()都拿到一个新的枚举器,这样每次遍历都能从头读取模拟数据。
修复方案
修改GetMockSet里的GetEnumerator Setup代码,改用lambda表达式返回新的枚举器:
mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(() => querable.GetEnumerator());
这样每次调用GetEnumerator()都会生成一个新的枚举器实例,不管你调用多少次ToList()或者Count(),都能正常获取到那5条模拟员工数据。
额外说明:为什么Employees属性没出问题?
看你代码里对mockDb.SetupGet(i => i.Employees)用的是Returns(() => GetMockSet(Employees).Object)——这里用了lambda表达式,所以每次访问_context.Employees都会生成一个全新的MockDbSet实例,里面的枚举器也是新的,所以如果用_context.Employees.ToList()两次,不会出现数据消失的问题。但Set<Employee>()的Setup是直接返回一个固定的MockDbSet实例,所以才会暴露枚举器复用的问题。
内容的提问来源于stack exchange,提问作者Donald N. Mafa

