在 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/单元测试的滥用,所以我的理由是,我可以这样做:

[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 ——除了验证调用之外,所有这些我都可以做。

44866 次浏览

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

这实际上是单元测试滥用,因为您要在一个测试中验证两件事情。如果你把 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());
}

您可以使用 Callback 方法而不是 Verify,并计算调用次数。

这在 Moq 快速入门页面上得到了证明,因此:

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

我也见证了泰晤士报。确切地说,(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);
}

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

如果你成功了就告诉我

现在 Moq 支持这种做法

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

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

旧答案

我认为在这篇文章创建很久之后,他们添加了 OP 要求的功能,有一个名为 Moq.MockExtension.ResetCall ()的 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());
}

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

someMockObject.ResetCalls();

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

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

foo.Invocations.Clear();