我正在使用moq版本4.2来测试web api 2的一些控制器,其中EF作为ORM。由于很多测试都需要模拟IEntityRepository<User>
所以我在一个名为TestHelper
的单独类中使用静态方法来创建这样一个假存储库,以便我可以在需要的所有测试中调用此方法。这是静态方法:
internal static IEntityRepository<User> GetSingleUserMockRepository(User user, bool noTracking = true, params Expression<Func<User, object>>[] properties)
{
var userRepository = new Mock<IEntityRepository<User>>();
if (properties.Any())
userRepository.Setup(x => x.GetSingleInclude(user.Key, noTracking, properties)).Returns(Task.FromResult(user));
else
userRepository.Setup(x => x.GetSingle(user.Key, noTracking)).Returns(Task.FromResult(user));
return userRepository.Object;
}
我在这里没有显示其他方法细节,因为我们认为它们与我的问题无关。问题是在我的测试中调用这个静态方法并将返回的用户存储库传递给控制器时,测试将失败,因为它无法找到用户,即Setup
没有工作!但是,如果我在我的测试中重复测试实现来创建模拟存储库作为局部变量;一切正常。
即,这不起作用(其中Devices
是用户实体的导航属性,同样不认为此处的细节很重要),例如如果我将返回的userRepository
传递给我的控制器,测试将失败。:
var userRepository = TestHelper.GetSingleUserMockRepository(user, true, x=> x.Devices);
但这有效,例如如果我只是在测试中使用局部变量并将userRepository.Object
传递给我的控制器,一切都按预期工作:
var userRepository = new Mock<IEntityRepository<User>>();
userRepository.Setup(x => x.GetSingleInclude(user.Key, true, u => u.Devices)).Returns(Task.FromResult(user));
有人可以解释一下原因吗?我认为这两种方式是相同的,但显然不是在实践中。
答案 0 :(得分:1)
这可能不是一个完整的答案(但是),但仍然很长时间以适应Stack Overflow评论。
当你这样做时:
userRepository.Setup(x => x.GetSingleInclude(user.Key, noTracking, properties)).Returns(...);
你说的是:当使用GetSingleInclude
和user.Key
调用noTracking
,特别是properties
表达式数组时,Moq将返回你指定。
但是,如果Moq接到GetSingleInclude
的调用,其中一个或多个参数等于您提供的值,则您的设置无关紧要,而Moq则返回默认值为null
。
(如果您使用了MockBehavior.Strict
,那么它会抛出一个异常,而不是默默地返回null
,这对于理解这个问题会更有帮助。)
那么两个表达式树数组何时相等? 如果 Moq对数组使用默认的相等比较器,那么当它们是相同的数组实例时,它们只是“相等”。 如果 Moq使用了一些逐项比较,那么它将归结为每个表达式树是否等于引用(即相同的Expression<>
实例)。无论如何,你可能会遇到麻烦。
问题是为什么它适用于另一种情况。当GetSingleInclude
(表达式树)的数量很少时,properties
是否有特殊的重载?
从this question运行代码的结果是什么?
我说你可能遇到与线程
Mocking and verifying call to method containing an Expression<Func<T,bool>>
parameter中相同的问题。