使用Moq验证呼叫是否按正确的顺序进行

g t*_*g t 55 c# nunit unit-testing moq sequential

我需要测试以下方法:

CreateOutput(IWriter writer)
{
    writer.Write(type);
    writer.Write(id);
    writer.Write(sender);

    // many more Write()s...
}
Run Code Online (Sandbox Code Playgroud)

我创建了一个Moq'd IWriter,我想确保Write()以正确的顺序调用这些方法.

我有以下测试代码:

var mockWriter = new Mock<IWriter>(MockBehavior.Strict);
var sequence = new MockSequence();
mockWriter.InSequence(sequence).Setup(x => x.Write(expectedType));
mockWriter.InSequence(sequence).Setup(x => x.Write(expectedId));
mockWriter.InSequence(sequence).Setup(x => x.Write(expectedSender));
Run Code Online (Sandbox Code Playgroud)

但是,第二次调用Write()in CreateOutput()(写入id值)会抛出一条MockException消息" IWriter.Write()调用失败,模拟行为为Strict.模拟上的所有调用都必须有相应的设置. "

我也发现很难找到任何明确的,最新的Moq序列文档/示例.

我做错了什么,或者我不能使用相同的方法设置序列?如果没有,有没有我可以使用的替代品(最好使用Moq/NUnit)?

Ser*_*kiy 61

在同一个模拟器上使用MockSequence时有bug .它肯定会在Moq库的后续版本中修复(您也可以通过更改Moq.MethodCall.Matches实现来手动修复它).

如果您只想使用Moq,那么您可以通过回调验证方法调用顺序:

int callOrder = 0;
writerMock.Setup(x => x.Write(expectedType)).Callback(() => Assert.That(callOrder++, Is.EqualTo(0)));
writerMock.Setup(x => x.Write(expectedId)).Callback(() => Assert.That(callOrder++, Is.EqualTo(1)));
writerMock.Setup(x => x.Write(expectedSender)).Callback(() => Assert.That(callOrder++, Is.EqualTo(2)));
Run Code Online (Sandbox Code Playgroud)

  • 这里有一个警告,如果从不调用`Write()`方法,这些回调都没有机会断言任何东西.一定要添加一个catch所有验证方法至少调用一次. (3认同)

g t*_*g t 10

我设法得到了我想要的行为,但它需要从http://dpwhelan.com/blog/software-development/moq-sequences/下载第三方库.

然后可以使用以下方法测试序列:

var mockWriter = new Mock<IWriter>(MockBehavior.Strict);
using (Sequence.Create())
{
    mockWriter.Setup(x => x.Write(expectedType)).InSequence();
    mockWriter.Setup(x => x.Write(expectedId)).InSequence();
    mockWriter.Setup(x => x.Write(expectedSender)).InSequence();
}
Run Code Online (Sandbox Code Playgroud)

我已经添加了这个作为答案,部分是为了帮助记录这个解决方案,但我仍然对是否可以单独使用Moq 4.0实现类似的东西感兴趣.

我不确定Moq是否仍处于开发阶段,但是修复问题MockSequence,或者在Moq中包含moq-sequences扩展可以很好地看到.


Jus*_*der 8

我写了一个扩展方法,它将根据调用顺序断言.

public static class MockExtensions
{
  public static void ExpectsInOrder<T>(this Mock<T> mock, params Expression<Action<T>>[] expressions) where T : class
  {
    // All closures have the same instance of sharedCallCount
    var sharedCallCount = 0;
    for (var i = 0; i < expressions.Length; i++)
    {
      // Each closure has it's own instance of expectedCallCount
      var expectedCallCount = i;
      mock.Setup(expressions[i]).Callback(
        () =>
          {
            Assert.AreEqual(expectedCallCount, sharedCallCount);
            sharedCallCount++;
          });
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

它的工作原理是利用闭包对作用域变量的工作方式.由于sharedCallCount只有一个声明,因此所有闭包都将引用同一个变量.使用expectedCallCount,在循环的每次迭代中实例化一个新实例(而不是简单地在闭包中使用i).这样,每个闭包都有一个i范围的副本,仅在调用表达式时与sharedCallCount进行比较.

这是扩展的小单元测试.请注意,此方法在您的设置部分调用,而不是您的断言部分.

[TestFixture]
public class MockExtensionsTest
{
  [TestCase]
  {
    // Setup
    var mock = new Mock<IAmAnInterface>();
    mock.ExpectsInOrder(
      x => x.MyMethod("1"),
      x => x.MyMethod("2"));

    // Fake the object being called in order
    mock.Object.MyMethod("1");
    mock.Object.MyMethod("2");
  }

  [TestCase]
  {
    // Setup
    var mock = new Mock<IAmAnInterface>();
    mock.ExpectsInOrder(
      x => x.MyMethod("1"),
      x => x.MyMethod("2"));

    // Fake the object being called out of order
    Assert.Throws<AssertionException>(() => mock.Object.MyMethod("2"));
  }
}

public interface IAmAnInterface
{
  void MyMethod(string param);
}
Run Code Online (Sandbox Code Playgroud)


Grz*_*ski 7

最近,我为 Moq 整合了两个功能:VerifyInSequence() 和VerifyNotInSequence()。它们甚至可以与 Loose Mocks 一起使用。但是,这些仅在起订量存储库分支中可用:

https://github.com/grzesiek-galezowski/moq4

并等待更多评论和测试,然后再决定是否可以将其包含在官方起订量发布中。然而,没有什么可以阻止您以 ZIP 形式下载源代码,将其构建到 dll 中并尝试一下。使用这些功能,您需要的序列验证可以写成这样:

var mockWriter = new Mock<IWriter>() { CallSequence = new LooseSequence() };

//执行必要的调用

mockWriter.VerifyInSequence(x => x.Write(expectedType));
mockWriter.VerifyInSequence(x => x.Write(expectedId));
mockWriter.VerifyInSequence(x => x.Write(expectedSender));

(请注意,您可以根据需要使用其他两个序列。松散序列将允许您要验证的序列之间的任何调用。StrictSequence 不允许这样做,而 StrictAnytimeSequence 与 StrictSequence 类似(已验证的调用之间没有方法调用),但允许前面有任意数量的任意调用的序列。

如果您决定尝试此实验性功能,请评论您的想法: https: //github.com/Moq/moq4/issues/21

谢谢!


znn*_*znn 7

我刚刚遇到了类似的情况,并受到已接受答案的启发,我使用了以下方法:

//arrange
var someServiceToTest = new SomeService();

var expectedCallOrder = new List<string>
{
    "WriteA",
    "WriteB",
    "WriteC"
};
var actualCallOrder = new List<string>();

var mockWriter = new Mock<IWriter>();
mockWriter.Setup(x => x.Write("A")).Callback(() => { actualCallOrder.Add("WriteA"); });
mockWriter.Setup(x => x.Write("B")).Callback(() => { actualCallOrder.Add("WriteB"); });
mockWriter.Setup(x => x.Write("C")).Callback(() => { actualCallOrder.Add("WriteC"); });

//act
someServiceToTest.CreateOutput(_mockWriter.Object);

//assert
Assert.AreEqual(expectedCallOrder, actualCallOrder);
Run Code Online (Sandbox Code Playgroud)


Ufu*_*arı 5

最简单的解决方案是使用Queue

var expectedParameters = new Queue<string>(new[]{expectedType,expectedId,expectedSender});
mockWriter.Setup(x => x.Write(expectedType))
          .Callback((string s) => Assert.AreEqual(expectedParameters.Dequeue(), s));
Run Code Online (Sandbox Code Playgroud)