【问题标题】:How can I use It.IsAny as a parameter?如何使用 It.IsAny 作为参数?
【发布时间】:2019-02-14 08:14:46
【问题描述】:

我有一个调用 MyClass 模拟的测试类,我之前有 SetupDoStuffADoStuffB

我尝试将多个 Verify 调用包装在一个方法中,如下所示:

void VerifyMany(int input)
{
    _myClassMock.Verify(ic => ic.DoStuffA(input), Times.Once());
    _myClassMock.Verify(ic => ic.DoStuffB(input), Times.Once());
}

如果我使用 It.IsAny<int>() 作为输入调用我的方法 - VerifyMany(It.IsAny<int>()) - 我的测试没有通过,但是如果我直接使用 It.IsAny 调用 Verify 方法,它将起作用:

_myClassMock.Verify(ic => ic.DoStuffA(It.IsAny<int>()), Times.Once());
_myClassMock.Verify(ic => ic.DoStuffB(It.IsAny<int>()), Times.Once());

我从this 问题的回答中了解到,当指定为设置/验证时,Moq 在表达式中处理 It.IsAny 的方式不同,是否有任何解决方法?

【问题讨论】:

  • 你能分享一下方法的调用吗?
  • 为避免混淆,请在调用VerifyMany 之前声明您正在处理.Setup 或在您的示例中提供它。看来,它“会”还是“不会”起作用并不是那么确定的。我对您在这里工作的尝试造成了困惑。
  • @BrettCaswell 添加了一些说明。
  • 您实际链接的那个问题存在下划线问题.. 仅仅因为 It.IsAny 评估为 0 并不意味着它对于他的验证调用仍然不可行,而不是通过123 验证,OP 应该刚刚使用 myValue。如果您将一个变量传递到您的模拟中,那么您也应该使用该变量进行验证。

标签: c# moq


【解决方案1】:

这不起作用的原因:

void VerifyMany(int input)
{
    _myClassMock.Verify(ic => ic.DoStuffA(input), Times.Once());
    _myClassMock.Verify(ic => ic.DoStuffB(input), Times.Once());
}

是因为It.IsAny&lt;int&gt;()(简单调用时)返回 0。所以本质上,当使用It.IsAny&lt;int&gt;() 调用VerifyMany 时,您将0 传递给VerifyMany。这反过来意味着 DoStuffA(0)DoStuffB(0) 已经过验证(不是您可能想要的任何整数值)。

另一个调用:

_myClassMock.Verify(ic => ic.DoStuffA(It.IsAny<int>()), Times.Once());
_myClassMock.Verify(ic => ic.DoStuffB(It.IsAny<int>()), Times.Once());

确实有效,因为 ic =&gt; ic.DoStuffA(It.IsAny&lt;int&gt;() 部分从未被直接调用。它变成了一个表达式树,Moq 只走(或访问,如果你喜欢)那个表达式树。这意味着它将在表达式树中找到It.IsAny&lt;int&gt;(),然后能够使用任何整数值验证对DoStuffA(它也在同一表达式树中找到)的调用。 (有关表达式树的更多信息,请随时read this

您可以通过创建一个方法来完成这项工作,该方法将对Verify 的调用抽象出来并接受如下表达式:

void VerifyOnce(Expression<Action<ClassMockIsBasedOn>> callToVerify)
{
    _myClassMock.Verify(callToVerify, Times.Once());
}

这允许你这样称呼它:

VerifyOnce(ic => ic.DoStuffA(It.IsAny<int>())

您还可以扩展VerifyOnce 示例以接受多个表达式。这将允许您在一行中验证 DoStuffADoStuffB

void VerifyOnce(params Expression<Action<ClassMockIsBasedOn>>[] callsToVerify)
{
    foreach(var callToVerify in callsToVerify) 
    {
        _myClassMock.Verify(callToVerify, Times.Once());
    }
}

这将允许这样的调用:

VerifyOnce(ic => ic.DoStuffA(It.IsAny<int>(),
           ic => ic.DoStuffB(It.IsAny<int>());

当然你可以用泛型替换ClassMockIsBasedOn。并添加一个重载,允许方法返回值(而不是 void)或接受多个参数,如 Brett 在 cmets 中建议的那样。

【讨论】:

  • 可以使用泛型改进您的抽象调用:void VerifyOnce&lt;T&gt;(Expression&lt;Action&lt;T&gt;&gt; callToVerify) 可以在这里使用。也 void VerifyOnce&lt;TFrom, TTo&gt;(Expression&lt;Func&lt;TFrom,TTo&gt;&gt; callToVerify) 作为重载。不过,您可能必须在调用以消除歧义时添加约束和/或提供类型。
  • 我可以将表达式作为参数传递,但是我无法使用相同的表达式执行两个不同的 MyClass 方法。
  • @JohnoBoy 我认为文森特提供了一个传递表达式并在 .Verify 调用中使用它的示例,但是您可以只传递一个表达式数组并使用 foreach 循环遍历它们.. @ 987654345@ ...
  • 这里完全同意Brett,我用一个例子来扩展答案
【解决方案2】:

It.IsAny 如果在 Setup 构造中使用,则仅允许 Moq 匹配未来的方法调用调用,如果您的 Setup 未设置 @987654326,则不能使用 VerifyAssert 进行匹配@ 然而。因为It.IsAny总是返回一个默认的输入值。

因此,如果您想在Verify 处使用It.IsAny,则应首先将Setup 用作目标方法:

正确

_myClassMock.Setup(ic => ic.DoStuffA(It.IsAny<int>())).Returns(// Something here);
_myClassMock.Verify(ic => ic.DoStuffA(It.IsAny<int>()), Times.Once());

错误

_myClassMock.Setup(ic => ic.DoStuffA(1)).Returns(// Something here);
_myClassMock.Verify(ic => ic.DoStuffA(It.IsAny<int>()), Times.Once());

【讨论】:

  • 您能否详细说明一下,为什么它不能与Verify 一起使用?
  • 这个答案实际上超出了这个问题的范围。 OP 本质上是在寻求一种将Is.IsAny&lt;int&gt; 传递给他的VerifyMany 问题的方法,并对其进行正确评估。 @JohnoBoy 应该声明他正在另一个函数范围内执行 .Setup,或者在他的示例中提供了这一点,以避免导致此答案的混淆。
  • 补充:正如@BrettCaswell 提到的,我之前已经设置了我的模拟。但是,即使我之前没有使用 It.IsAny 设置 DoStuff,我仍然可以使用 IsAny 进行验证。我已经测试过了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-07-04
  • 2017-11-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-11
相关资源
最近更新 更多