【问题标题】:Reset mock verification in Moq?在 Moq 中重置模拟验证?
【发布时间】:2011-05-08 23:59:31
【问题描述】:

设置如下:

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
}

基本上我想在// stuff here 输入一些代码以使foo.Verify(x =&gt; x.Fizz(), Times.Never()) 通过。

因为这可能构成最小起订量/单元测试滥用,我的理由是我可以这样做:

[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());
}

基本上,我有一个状态对象,需要做相当多的工作(无论是在制作各种模拟对象还是其他方面)将其推入 State1。然后我想测试从 State1 到 State2 的转换。我宁愿重复使用 State1 测试,而不是复制或抽象代码,而是将其推送到 State2 并执行我的 Asserts - 除了验证调用之外,我可以做所有这些。

【问题讨论】:

    标签: c# unit-testing moq


    【解决方案1】:

    现在起订量支持此功能

    在最新版本的库上使用.Invocations.Clear()

    var foo = new Mock<foo>();
    foo.Invocations.Clear();
    

    旧答案

    我认为在创建这篇文章很久之后,他们添加了 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());
    }
    

    【讨论】:

    • Invocations.Clear() 不适用于 Moq 4.10.1。在构造函数和单元测试设置中定义的模拟具有 Invocations.Clear()。测试被忽略。
    • Invocations.Clear, github.com/moq/moq4/issues/733 有一个未解决的问题
    • 是的,Moq.MockExtensions.ResetCalls() 至少在Moq 4.13.1 中被标记为Obsolete,并建议使用Invocation.Clear()
    【解决方案2】:

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

    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());
    

    话虽如此,我仍然会为此创建两个单独的测试。作为两个测试,更容易看出是转换到状态 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"); 
    }
    

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

    再次查看您的代码,因为您正在调用一个方法来验证它是否在模拟上调用另一种方法,我认为您需要将 CallBase 设置为 true 以便基础 DoStuffToPushIntoState2 是调用而不是模拟的覆盖。

    【讨论】:

    • 你试过了吗?因为对我来说第二个 foo.Verify(x =&gt; x.Fizz(), Times.Once()); 总是返回 true - 即使你不打电话给 objectUnderTest.DoStuffToPushIntoState2();
    • @fostandy,如果您调用DoStuffToPushIntoState1(),并且调用Fizz 一次,那么Times.Once() 将通过。如果您注释掉 DoStuffToPushIntoState2() 并且没有其他调用 FizzTimes.Once() 将继续传递。我将编辑我的答案以添加我测试过的代码。
    • @adrift 我遇到了同样的问题,偶然发现了一个神奇的 MOQ 扩展函数,叫做 Moq.MockExtensions.ResetCalls()。它可能是在这篇文章之后很久才创建的。
    • 这个答案已经过时了,你现在可以做Invocations.Clear(),见this answer
    【解决方案3】:

    @stackunderflow 的补充回答(哈哈,漂亮的 nick :))

    在 Moq 的更高版本中,Moq.MockExtensions.ResetCalls() 被标记为已过时。应该改用mock.Invocations.Clear()

    foo.Invocations.Clear();
    

    【讨论】:

      【解决方案4】:

      我还目睹了 Times.Exactly(1) 在使用 MoQ 的单元测试中验证失败,并出现“被调用 2 次”错误消息。我认为这是 MoQ 中的一个错误,因为我希望每次测试运行时都有干净的模拟状态。

      我的工作是在测试设置中分配一个新的模拟实例和测试目标。

      private Mock<IEntityMapper> entityMapperMock;
      private OverdraftReportMapper target;
      
      [SetUp]
      public void TestSetUp()
      {
        entityMapperMock = new Mock<IEntityMapper>();
        target = new OverdraftReportMapper(entityMapperMock.Object);
      } 
      

      【讨论】:

        【解决方案5】:

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

        someMockObject.ResetCalls();
        

        【讨论】:

        • 这已被弃用,请阅读上文了解新的调用方法
        【解决方案6】:

        这确实是单元测试滥用,因为您在一个测试中验证了两件事。如果您将ObjectUnderTest 初始化从测试中取出并进入通用设置方法,您的生活会轻松得多。然后,您的测试变得更具可读性并且彼此独立。

        除了生产代码之外,测试代码还应该针对可读性和隔离性进行优化。对系统行为某一方面的测试不应影响其他方面。将通用代码重构为设置方法确实比尝试重置模拟对象要容易得多。

        ObjectUnderTest _objectUnderTest;
        
        [Setup] //Gets run before each test
        public void Setup() {
            var foo = new Mock<IFoo>(); //moq by default creates loose mocks
            _objectUnderTest = new ObjectUnderTest(foo.Object);
        }
        [Test]
        public void DoStuffToPushIntoState1ShouldCallFizz() {
            _objectUnderTest.DoStuffToPushIntoState1(); // this is various lines of code and setup
        
            foo.Verify(x => x.Fizz());
        }
        [Test]
        public void DoStuffToPushIntoState2ShouldntCallFizz() {
        {
            objectUnderTest.DoStuffToPushIntoState2(); // more lines of code
            foo.Verify(x => x.Fizz(), Times.Never());
        }
        

        【讨论】:

        • 对不起,我知道这有点老了,但我认为这并不能解决问题中的问题,我想知道是否有一种简单的方法可以扩展它。具体来说,DoStuffToPushIntoState2 仅在对象已经处于状态 1 时才起作用。如果对象的 API 只允许它通过调用 DoStuffToPushIntoState1 转换到状态 1,并且这样做总是调用 Fizz,那么即使您在期间执行了 DoStuffToPushIntoState1设置,您仍然无法区分由 DoStuffToPushIntoState2 引起的对 Fizz 的调用。
        【解决方案7】:

        您可以使用回调方法而不是验证,并计算调用次数。

        这在Moq Quick Start page 上进行了演示,因此:

        // returning different values on each invocation
        var mock = new Mock<IFoo>();
        var calls = 0;
        mock.Setup(foo => foo.GetCountThing())
            .Returns(() => calls)
            .Callback(() => calls++);
        // returns 0 on first invocation, 1 on the next, and so on
        Console.WriteLine(mock.Object.GetCountThing());
        

        【讨论】:

          【解决方案8】:

          下一个方法对我来说很好(使用 Moq.Sequence)

              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());
          
                  // Some cool stuff
          
                  using (Sequence.Create())
                  {
                      foo.Setup(x => x.Fizz()).InSequence(Times.Never())
                      objectUnderTest.DoStuffToPushIntoState2(); // more lines of code
                  }
              }
          

          让我知道它是否适合你

          【讨论】:

            猜你喜欢
            • 2013-07-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-11-14
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多