在Moq中重置模拟验证?

fos*_*ndy 58 c# unit-testing moq

设置如下:

public interface IFoo
{
    void Fizz();
}

[Test]
public void A()
{
    var foo = new Mock<IFoo>(MockBehavior.Loose);

    foo.Object.Fizz();

    foo.Verify(x => x.Fizz());

    // stuff here

    foo.Verify(x => x.Fizz(), Times.Never()); // currently this fails
}
Run Code Online (Sandbox Code Playgroud)

基本上我想// stuff herefoo.Verify(x => x.Fizz(), Times.Never())传递中输入一些代码.

因为这可能构成moq/unit测试滥用,我的理由是我可以这样做:

[Test]
public void Justification()
{
    var foo = new Mock<IFoo>(MockBehavior.Loose);
    foo.Setup(x => x.Fizz());

    var objectUnderTest = new ObjectUnderTest(foo.Object);

    objectUnderTest.DoStuffToPushIntoState1(); // this is various lines of code and setup

    foo.Verify(x => x.Fizz());

    // reset the verification here

    objectUnderTest.DoStuffToPushIntoState2(); // more lines of code

    foo.Verify(x => x.Fizz(), Times.Never());
}
Run Code Online (Sandbox Code Playgroud)

基本上,我有一个状态对象,其中需要进行相当多的工作(在制作各种模拟对象和其他模拟对象方面)将其推入State1.然后我想测试从State1到State2的转换.我没有重复或抽象代码,而是只重新使用State1测试,将其推入State2并执行我的断言 - 除了验证调用之外,我可以做所有这些操作.

sta*_*low 114

我想在创建这篇文章很久之后,他们添加了OP要求的功能,有一个名为Moq.MockExtensions.ResetCalls()的Moq扩展方法.

使用此方法,您可以完全按照您的意愿执行,如下所示:

[Test]
public void Justification()
{
    var foo = new Mock<IFoo>(MockBehavior.Loose);
    foo.Setup(x => x.Fizz());

    var objectUnderTest = new ObjectUnderTest(foo.Object);

    objectUnderTest.DoStuffToPushIntoState1(); // this is various lines of code and setup

    foo.Verify(x => x.Fizz());

    foo.ResetCalls(); // *** Reset the verification here with this glorious method ***

    objectUnderTest.DoStuffToPushIntoState2(); // more lines of code

    foo.Verify(x => x.Fizz(), Times.Never());
}
Run Code Online (Sandbox Code Playgroud)

更新

现在我们应该在库的最新版本上使用.Invocations.Clear()代替.ResetCalls():

foo.Invocations.Clear()
Run Code Online (Sandbox Code Playgroud)

  • `Invocations.Clear` 有一个未解决的问题,https://github.com/moq/moq4/issues/733 (2认同)
  • 是的,“Moq.MockExtensions.ResetCalls()”至少在“Moq 4.13.1”中被标记为“已过时”,并建议使用“Initation.Clear()”。 (2认同)

Cod*_*ler 10

添加@stackunderflow 的回答(哈哈,不错的昵称:))

在 Moq 的后续版本中,Moq.MockExtensions.ResetCalls()被标记为已过时。mock.Invocations.Clear()应该使用:

foo.Invocations.Clear();
Run Code Online (Sandbox Code Playgroud)


Jef*_*ata 7

我不认为你可以重置像这样的模拟.相反,如果你知道Fizz在转换到状态1时应该调用一次,你可以像这样进行验证:

objectUnderTest.DoStuffToPushIntoState1();
foo.Verify(x => x.Fizz(), Times.Once());  // or however many times you expect it to be called

objectUnderTest.DoStuffToPushIntoState2();
foo.Verify(x => x.Fizz(), Times.Once());
Run Code Online (Sandbox Code Playgroud)

话虽如此,我仍然会为此创建两个单独的测试.作为两个测试,更容易看出转换到状态1是否失败,或者转换到状态2是否失败.此外,当像这样一起测试时,如果您转换到状态1失败,则测试方法退出,并且您转换到状态2不会被测试.

编辑

作为一个例子,我用xUnit测试了以下代码:

[Fact]
public void Test()
{
    var foo = new Mock<IFoo>(MockBehavior.Loose);

    foo.Object.Fizz();
    foo.Verify(x => x.Fizz(), Times.Once(), "Failed After State 1");

    // stuff here
    foo.Object.Fizz();
    foo.Verify(x => x.Fizz(), Times.Once(), "Failed after State 2"); 
}
Run Code Online (Sandbox Code Playgroud)

此测试失败,并显示消息"状态2后失败".这模拟了如果你的方法将foo推入状态2调用会发生什么Fizz.如果是,第二个Verify将失败.

再次,在你的代码看,因为你调用一个方法来验证它/不会调用其他方法的模拟,我想你需要设置CallBase,以true使基地DoStuffToPushIntoState2被称为而非模拟的覆盖.

  • @adrift我遇到了同样的问题,并偶然发现了一个名为Moq.MockExtensions.ResetCalls()的神奇MOQ扩展函数.它可能是在这篇文章之后很久就创建的. (3认同)
  • 这个答案已经过时了,您现在可以执行`Initations.Clear()`,请参阅[这个答案](/sf/answers/1648445011/) (2认同)

Aus*_*nTX 5

取决于你使用的 Mock 版本,我确信我们可以做到这一点

someMockObject.ResetCalls();
Run Code Online (Sandbox Code Playgroud)