在Moq中重置模拟验证?

时间:2010-11-12 10:27:57

标签: 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
}

基本上我想在// stuff here输入一些代码,以便foo.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());
}

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

8 个答案:

答案 0 :(得分:106)

我认为在创建这篇文章很久之后,他们添加了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()而不是.ResetCalls():

foo.Invocations.Clear()

答案 1 :(得分:7)

我认为你不能像这样重置一个模拟器。相反,如果您知道在转换到状态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而不是模拟的覆盖。

答案 2 :(得分: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);
} 

答案 3 :(得分:3)

您可以使用Callback方法代替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());

答案 4 :(得分:3)

取决于你使用哪个版本的模拟,我知道我们可以做到这一点

someMockObject.ResetCalls();

答案 5 :(得分:2)

这确实是单元测试滥用,因为您在一次测试中验证了两件事。如果您将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());
}

答案 6 :(得分:2)

通过@stackunderflow回答的问题(哈哈,昵称:))

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

foo.Invocations.Clear();

答案 7 :(得分:0)

接下来的方法适用于我(使用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
        }
    }

让我知道它是否适合您