给定一个我正在测试的简单类型,比如
interface IMyRepo
{
MyObj GetBy(Expression<Func<MyObj, bool> predicate);
}
因此标准模拟设置工作正常:
_repoMock.Setup(x => x.GetBy(y => y.Prop=="A")).Returns(myObjInstance);
例如,它匹配:_repo.GetBy(x => x.Prop=="A")
但是,如果我有一个辅助方法用于我的设置:_repoMock.Setup(Helpers.GenerateGetByMock<IMyRepo, MyObj>(y => y.Prop=="A"));
在哪里
Expression<Func<T, R>> GenerateGetByMock<T, R>(Expression<Func<R,bool>> pred) where T : IMyRepo where R : class
{
return x => x.GetBy(pred);
}
这在测试中不_repo.GetBy(x => x.Prop=="A")
匹配!
生成的表达式有什么不同?为什么我必须使用显式内联版本?有没有办法使用辅助方法?
与辅助方法相比:
如屏幕截图所示,它们是两个不同的表达式。每个
Expression<T>
对象都是一个小的表达式树,Moq 可以比较它来确定它是否与另一个表达式树匹配。我很久没有看过 Moq 代码了,但我记得它不会评估表达式。它会比较它们以查看它们是否足够相似。就像在基本编译器理论中一样,可以实现各种编译器优化来简化或概括表达式树,但我不认为 Moq 会“遵循”并内联方法调用。
毕竟,为了内联方法调用,编译器或解释器需要知道这样做是否安全。情况并不总是如此。内联方法不安全的一个简单情况是,当方法中的局部变量的名称与更广泛上下文中的变量相同时。因此,在这种情况下,内联至少需要变量重命名。
坦白说,如果 Moq 实现了表达式树优化到这种复杂程度,我会感到惊讶(即使从编译器理论来看,这实际上处于本科水平,因此甚至没有那么复杂)。这并不是说 Moq 不是一个复杂的库,而是说它从根本上来说不是一个编译器;它有不同的用途。
一般来说,一旦您使用动态模拟库(无论是 Moq、NSubstitute、FakeItEasy 等 - 这并不重要)进入过于棘手的领域,就将其视为反馈,即您正将自己带入维护噩梦的境地。考虑不同的设计或测试策略。
我强烈建议您考虑基于状态的测试,而不是基于交互的测试,但作为一个较小的、孤立的技巧,您有时可以使用Resemblance 习语来解决与 Moq 相关的某些问题。然而,我不确定 Resemblance 是否会解决这个特定问题;我还没有详细分析过它,而且我不确定我是否有足够的信息来这样做。
问题在于编译器如何
Expression<Func<T, R>>
从你的辅助方法生成:它不能编译,所以大概你的意思应该是这样的:
假设您的示例中有这些类型:
由于某种原因,编译器不够智能,无法做到这一点:
如果你使用上述方法,事情就会按预期进行。
如果您仍然想依靠编译器完成大部分工作,那么您需要利用实现
ExpressionVisitor
来修改参数常量部分,并且可能最灵活的方法是为该谓词引用创建一个扩展方法:然后你需要稍微修改一下你的方法:
OP 的小提琴 – 经过修改以使用扩展方法 – 可以工作。
一些手动测试代码: