代码之家  ›  专栏  ›  技术社区  ›  fostandy

重置Moq中的模拟验证?

  •  50
  • fostandy  · 技术社区  · 14 年前

    按原样设置:

    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 => x.Fizz(), Times.Never()) 通过。

    因为这可能构成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());
    
        // reset the verification here
    
        objectUnderTest.DoStuffToPushIntoState2(); // more lines of code
    
        foo.Verify(x => x.Fizz(), Times.Never());
    }
    

    基本上,我有一个state对象,其中需要做一些工作(包括制作各种模拟对象和其他虚张声势)来将其推入State1。然后我想测试从State1到State2的转换。与其复制或抽象代码,我更希望重用State1测试,不如将其推入State2并执行我的断言—除了验证调用之外,我可以执行所有这些操作。

    8 回复  |  直到 6 年前
        1
  •  7
  •   Jeff Ogata    14 年前

    我认为你不能重设这样的模拟。相反,如果你知道 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的方法调用 . 如果是,第二个 Verify

    再次查看您的代码,因为您正在调用一个方法来验证它是否在mock上调用另一个方法,我认为您需要设置 CallBase true 所以基地 DoStuffToPushIntoState2 调用而不是模拟的重写。

        2
  •  117
  •   strongwind stackunderflow    6 年前

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

    [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()而不是.ResetCalls():

    foo.Invocations.Clear()
    
        3
  •  4
  •   Henrik Voldby    13 年前

    我也见证了这些次,确切地说(1)在使用MoQ的单元测试中验证失败,错误消息是“被调用了2次”。我认为这是MoQ中的一个bug,因为我希望每次测试运行时都有干净的模拟状态。

    private Mock<IEntityMapper> entityMapperMock;
    private OverdraftReportMapper target;
    
    [SetUp]
    public void TestSetUp()
    {
      entityMapperMock = new Mock<IEntityMapper>();
      target = new OverdraftReportMapper(entityMapperMock.Object);
    } 
    
        4
  •  3
  •   Ed Harling    13 年前

    您可以使用回调方法而不是Verify,并对调用进行计数。

    这在 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());
    
        5
  •  3
  •   Johnny    6 年前

    取决于你用的是哪个版本的Mock,我肯定我们能做到

    someMockObject.ResetCalls();
    
        6
  •  2
  •   Igor Zevaka    14 年前

    这确实是单元测试滥用,因为您正在一个测试中验证两个东西。如果你接受 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());
    }
    
        7
  •  2
  •   CodeFuller    7 年前

    加上@stackunderflow的回答(哈哈,nice nick:)

    在以后的Moq版本中, Moq.MockExtensions.ResetCalls() 被标记为已过时。 mock.Invocations.Clear()

    foo.Invocations.Clear();
    
        8
  •  0
  •   Artiom    11 年前

    下一种方法对我很好(使用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
            }
        }