我可以通过 It.Is<> 传递给另一种方法来设置模拟吗?
Can I pass through It.Is<> to another method to set up a mock?
我有这个:
myObj.SetupSomething(It.IsAny<string>());
然后在 MyObj class:
public void SetupSomething(string s)
{
_someMock.Setup(c => c.DoWibble(s));
}
上面的代码不起作用,因为 It.IsAny<>
以 null
的形式出现,所以它使用 null
而不是任何字符串来设置它。如果我用这个替换它:
_someMock.Setup(c => c.DoWibble(It.IsAny<string>()));
然后它工作正常。所以我想知道,我能否将 It.IsAny<string>()
的表达式传递给我的方法,以便 Moq 仍然识别我在做什么,或者我是否需要制作一个额外的 SetupSomethingForAnyString()
方法(有点像讨厌)?
正如您发现的那样,简短的回答是 'No',不可能仅将原始 It.IsAny<string>()
结果作为参数传递给设置。但这并不是故事的真正结局。 Moq 如何检测表达式参数并将它们转换为最终在实际执行期间提供 return 值时使用的匹配器,存在一些令人难以置信的细微差别。如果您有时间,我绝对建议您查看源代码。那里发生了一些很酷的体操——这不仅仅是表达式树分析。
在最基本的层面上,在设置之外使用 It.IsAny<string>()
作为参数不起作用的原因与执行顺序有关。考虑:
var x = It.IsAny<string>();
mock.Setup(m => m.DoWibble(x));
对
mock.Setup(m => m.DoWibble(It.IsAny<string>()));
首先,It.IsAny<string>()
是在任何模拟上下文之外计算的,在模拟上下文之外,库不能真正 return 一个有意义的值,所以它只是 returns default(T)
。然后将该空值绑定为一个变量,该变量在引用的 lambda 表达式 m => m.DoWibble(x)
.
中捕获
整个表达式被传递给 Setup
方法,Moq 库将部分计算为参数提供的表达式,包括捕获的值 'x'。当前值为default(string)
,因此它将设置一个匹配器,该匹配器仅在传递空值时才起作用。
在第二个中,引用的表达式现在包括整个 m => m.DoWibble(It.IsAny<string>())
调用。现在,当 Moq 部分执行 It.IsAny<string>()
时,它处于可以与观察者合作执行副作用的上下文中,通知 Moq 库为该参数创建 'any' 匹配器。
但这意味着 Moq 可以检测到对 It.IsAny
方法的调用,即使它不是作为原始引用 lambda 的一部分直接调用的。这意味着这也适用:
Func<string> callIsAny = () => It.IsAny<string>();
mock.Setup(m => m.DoWibble(callIsAny.Invoke()));
这里的关键是 It.IsAny<string>()
实际上并没有在设置之前的语句中调用,而是仅在 Moq 分析模拟方法的参数时才被评估。因为它设置了一个检测上下文来查看何时调用 It.IsAny
,所以它可以正确地为这种情况创建匹配器。
请注意,这在最小起订量使用指南的任何地方都没有记录,所以我不确定这是否有效。 It.IsAny 的检测上下文可能仅用于设置事件处理程序。但这确实很酷!
相反,如果您想通过某个间接层实际传递参数匹配器,似乎最好利用 Moq 中已有的功能:It.Is
.
它将被定义为:
public void SetupSomething(Expression<Func<string, bool>> stringMatcher)
{
_someMock.Setup(m => m.DoWibble(It.Is(stringMatcher)));
}
像这样使用:
myObj.SetupSomething(_ => true); // match anything
myObj.SetupSomething(s => s == "a"); // match only calls with "a";
它的可读性不如使用普通的 It.IsAny
调用,但您可以通过将自己的匹配器 lambda 作为 属性 或方法调用(例如 Match.Any<string>()
)包含在内来缓解这种情况
在这一点上,它似乎更像是重新实现 Moq 的领域语言。是否值得付出努力取决于你。
我有这个:
myObj.SetupSomething(It.IsAny<string>());
然后在 MyObj class:
public void SetupSomething(string s)
{
_someMock.Setup(c => c.DoWibble(s));
}
上面的代码不起作用,因为 It.IsAny<>
以 null
的形式出现,所以它使用 null
而不是任何字符串来设置它。如果我用这个替换它:
_someMock.Setup(c => c.DoWibble(It.IsAny<string>()));
然后它工作正常。所以我想知道,我能否将 It.IsAny<string>()
的表达式传递给我的方法,以便 Moq 仍然识别我在做什么,或者我是否需要制作一个额外的 SetupSomethingForAnyString()
方法(有点像讨厌)?
正如您发现的那样,简短的回答是 'No',不可能仅将原始 It.IsAny<string>()
结果作为参数传递给设置。但这并不是故事的真正结局。 Moq 如何检测表达式参数并将它们转换为最终在实际执行期间提供 return 值时使用的匹配器,存在一些令人难以置信的细微差别。如果您有时间,我绝对建议您查看源代码。那里发生了一些很酷的体操——这不仅仅是表达式树分析。
在最基本的层面上,在设置之外使用 It.IsAny<string>()
作为参数不起作用的原因与执行顺序有关。考虑:
var x = It.IsAny<string>();
mock.Setup(m => m.DoWibble(x));
对
mock.Setup(m => m.DoWibble(It.IsAny<string>()));
首先,It.IsAny<string>()
是在任何模拟上下文之外计算的,在模拟上下文之外,库不能真正 return 一个有意义的值,所以它只是 returns default(T)
。然后将该空值绑定为一个变量,该变量在引用的 lambda 表达式 m => m.DoWibble(x)
.
整个表达式被传递给 Setup
方法,Moq 库将部分计算为参数提供的表达式,包括捕获的值 'x'。当前值为default(string)
,因此它将设置一个匹配器,该匹配器仅在传递空值时才起作用。
在第二个中,引用的表达式现在包括整个 m => m.DoWibble(It.IsAny<string>())
调用。现在,当 Moq 部分执行 It.IsAny<string>()
时,它处于可以与观察者合作执行副作用的上下文中,通知 Moq 库为该参数创建 'any' 匹配器。
但这意味着 Moq 可以检测到对 It.IsAny
方法的调用,即使它不是作为原始引用 lambda 的一部分直接调用的。这意味着这也适用:
Func<string> callIsAny = () => It.IsAny<string>();
mock.Setup(m => m.DoWibble(callIsAny.Invoke()));
这里的关键是 It.IsAny<string>()
实际上并没有在设置之前的语句中调用,而是仅在 Moq 分析模拟方法的参数时才被评估。因为它设置了一个检测上下文来查看何时调用 It.IsAny
,所以它可以正确地为这种情况创建匹配器。
请注意,这在最小起订量使用指南的任何地方都没有记录,所以我不确定这是否有效。 It.IsAny 的检测上下文可能仅用于设置事件处理程序。但这确实很酷!
相反,如果您想通过某个间接层实际传递参数匹配器,似乎最好利用 Moq 中已有的功能:It.Is
.
它将被定义为:
public void SetupSomething(Expression<Func<string, bool>> stringMatcher)
{
_someMock.Setup(m => m.DoWibble(It.Is(stringMatcher)));
}
像这样使用:
myObj.SetupSomething(_ => true); // match anything
myObj.SetupSomething(s => s == "a"); // match only calls with "a";
它的可读性不如使用普通的 It.IsAny
调用,但您可以通过将自己的匹配器 lambda 作为 属性 或方法调用(例如 Match.Any<string>()
)包含在内来缓解这种情况
在这一点上,它似乎更像是重新实现 Moq 的领域语言。是否值得付出努力取决于你。