使用 Moq 验证特定参数

Posted

技术标签:

【中文标题】使用 Moq 验证特定参数【英文标题】:Verifying a specific parameter with Moq 【发布时间】:2011-06-24 19:49:35 【问题描述】:
public void SubmitMessagesToQueue_OneMessage_SubmitSuccessfully()

    var messageServiceClientMock = new Mock<IMessageServiceClient>();
    var queueableMessage = CreateSingleQueueableMessage();
    var message = queueableMessage[0];
    var xml = QueueableMessageAsXml(queueableMessage);
    messageServiceClientMock.Setup(proxy => proxy.SubmitMessage(xml)).Verifiable();
    //messageServiceClientMock.Setup(proxy => proxy.SubmitMessage(It.IsAny<XmlElement>())).Verifiable();

    var serviceProxyFactoryStub = new Mock<IMessageServiceClientFactory>();
    serviceProxyFactoryStub.Setup(proxyFactory => proxyFactory.CreateProxy()).Returns(essageServiceClientMock.Object);
    var loggerStub = new Mock<ILogger>();

    var client = new MessageClient(serviceProxyFactoryStub.Object, loggerStub.Object);
    client.SubmitMessagesToQueue(new List<IMessageRequestDTO> message);

    //messageServiceClientMock.Verify(proxy => proxy.SubmitMessage(xml), Times.Once());
    messageServiceClientMock.Verify();

我开始使用起订量并有点挣扎。 我正在尝试验证 messageServiceClient 是否接收到正确的参数,即 XmlElement,但我找不到任何使其工作的方法。它仅在我不检查特定值时才有效。

有什么想法吗?

部分答案: 我找到了一种方法来测试发送给代理的 xml 是否正确,但我仍然认为它不是正确的方法。

public void SubmitMessagesToQueue_OneMessage_SubmitSuccessfully()

    var messageServiceClientMock = new Mock<IMessageServiceClient>();
    messageServiceClientMock.Setup(proxy => proxy.SubmitMessage(It.IsAny<XmlElement>())).Verifiable();
    var serviceProxyFactoryStub = new Mock<IMessageServiceClientFactory>();
    serviceProxyFactoryStub.Setup(proxyFactory => proxyFactory.CreateProxy()).Returns(messageServiceClientMock.Object);
    var loggerStub = new Mock<ILogger>();

    var client = new MessageClient(serviceProxyFactoryStub.Object, loggerStub.Object);
    var message = CreateMessage();
    client.SubmitMessagesToQueue(new List<IMessageRequestDTO> message);

    messageServiceClientMock.Verify(proxy => proxy.SubmitMessage(It.Is<XmlElement>(xmlElement => XMLDeserializer<QueueableMessage>.Deserialize(xmlElement).Messages.Contains(message))), Times.Once());

顺便问一下,如何从验证调用中提取表达式?

【问题讨论】:

【参考方案1】:

我一直在以同样的方式验证电话 - 我相信这是正确的做法。

mockSomething.Verify(ms => ms.Method(
    It.IsAny<int>(), 
    It.Is<MyObject>(mo => mo.Id == 5 && mo.description == "test")
  ), Times.Once());

如果您的 lambda 表达式变得笨拙,您可以创建一个函数,将 MyObject 作为输入并输出 true/false...

mockSomething.Verify(ms => ms.Method(
    It.IsAny<int>(), 
    It.Is<MyObject>(mo => MyObjectFunc(mo))
  ), Times.Once());

private bool MyObjectFunc(MyObject myObject)

  return myObject.Id == 5 && myObject.description == "test";

另外,请注意 Mock 的一个错误,错误消息指出该方法被多次调用,而根本没有调用它。他们现在可能已经修复了它 - 但如果您看到该消息,您可能会考虑验证该方法是否被实际调用。

编辑:这是一个多次调用 verify 的示例,用于那些您想要验证您是否为列表中的每个对象调用函数的场景(例如)。

foreach (var item in myList)
  mockRepository.Verify(mr => mr.Update(
    It.Is<MyObject>(i => i.Id == item.Id && i.LastUpdated == item.LastUpdated),
    Times.Once());

设置方法相同...

foreach (var item in myList) 
  var stuff = ... // some result specific to the item
  this.mockRepository
    .Setup(mr => mr.GetStuff(item.itemId))
    .Returns(stuff);

因此,每次为该 itemId 调用 GetStuff 时,它都会返回特定于该项目的内容。或者,您可以使用将 itemId 作为输入并返回内容的函数。

this.mockRepository
    .Setup(mr => mr.GetStuff(It.IsAny<int>()))
    .Returns((int id) => SomeFunctionThatReturnsStuff(id));

前段时间我在博客上看到的另一种方法(也许是 Phil Haack?)设置从某种出队对象返回 - 每次调用该函数时,它都会从队列中拉出一个项目。

【讨论】:

谢谢,这对我来说很有意义。我仍然无法理解的是何时在设置或验证中指定详细信息。这很令人困惑。目前,我只允许在设置中进行任何操作并在验证中指定值。 当有多个电话时,您认为我如何查看消息?客户端接收消息并可以创建多个 queueableMessages,这将在多个调用中结束,并且在每个调用中,我都必须检查不同的消息。总的来说,我仍在努力进行单元测试,我还不是很熟悉。 我不认为你应该如何做到这一点有灵丹妙药。这需要练习,你会开始变得更好。对我来说,只有当我有一些东西可以比较它们并且我还没有在另一个测试中测试那个参数时,我才会指定参数。至于多次调用,有几种方法。为了设置和验证多次调用的函数,我通常为我期望的每个调用调用 setup 或 verify (Times.Once()) - 通常使用 for 循环。您可以使用特定的参数来隔离每个调用。 我为多次调用添加了一些示例 - 请参阅上面的答案。 "另外,请注意 Mock 的一个错误,错误消息指出该方法在根本没有被调用时被多次调用。他们现在可能已经修复了它 - 但如果你请参阅该消息,您可能会考虑验证该方法是否被实际调用。” - 像这样的错误完全使模拟库恕我直言无效。具有讽刺意味的是,他们没有适当的测试代码:-)【参考方案2】:

我认为问题在于 Moq 将检查是否相等。而且,由于 XmlElement 不覆盖 Equals,它的实现将检查引用相等性。

你不能使用自定义对象,所以你可以覆盖equals吗?

【讨论】:

是的,我最终做到了。我意识到问题在于检查 Xml。在问题的第二部分中,我添加了一个可能的答案,将 xml 反序列化为对象【参考方案3】:

如果验证逻辑很重要,那么编写大型 lambda 方法会很麻烦(如您的示例所示)。您可以将所有测试语句放在一个单独的方法中,但我不喜欢这样做,因为它会破坏读取测试代码的流程。

另一种选择是在 Setup 调用中使用回调来存储传递给模拟方法的值,然后编写标准的 Assert 方法来验证它。例如:

// Arrange
MyObject saveObject;
mock.Setup(c => c.Method(It.IsAny<int>(), It.IsAny<MyObject>()))
        .Callback<int, MyObject>((i, obj) => saveObject = obj)
        .Returns("xyzzy");

// Act
// ...

// Assert
// Verify Method was called once only
mock.Verify(c => c.Method(It.IsAny<int>(), It.IsAny<MyObject>()), Times.Once());
// Assert about saveObject
Assert.That(saveObject.TheProperty, Is.EqualTo(2));

【讨论】:

这种方法的一大好处是,它会给您特定的测试失败,说明对象如何不正确(因为您正在单独测试每个对象)。 我以为我是唯一这样做的人,很高兴看到这是一种合理的方法! 我认为按照 Mayo 使用 It.Is(validator) 更好,因为它避免了将参数值保存为 lambda 的一部分的稍微尴尬的方式 这个线程是否安全,例如在并行运行测试时? @AntonTolken 我没试过,但在我的例子中,更新的对象是一个局部变量(saveObject),所以它应该是线程安全的。【参考方案4】:

一种更简单的方法是:

ObjectA.Verify(
    a => a.Execute(
        It.Is<Params>(p => p.Id == 7)
    )
);

【讨论】:

我似乎无法让它工作,我正在尝试验证我的方法是使用 Code.WRCC 作为参数调用的。但是我的测试总是通过,即使传递的参数是 WRDD..m.Setup(x =&gt; x.CreateSR(Code.WRDD)).ReturnsAsync(0); await ClassUnderTest.CompleteCC(accountKey, It.IsAny&lt;int&gt;(), mockRequest); m.Verify(x =&gt; x.CreateSR(It.Is&lt;Code&gt;(p =&gt; p == Code.WRCC)), Times.Once()); @Greg Quinn 仔细检查您的 TestMethod 的返回类型,它应该是 async Task,而不是 void【参考方案5】:

也有其中之一,但操作的参数是一个没有公共属性的接口。最终将 It.Is() 与单独的方法一起使用,并且在此方法中必须对接口进行一些模拟

public interface IQuery

    IQuery SetSomeFields(string info);


void DoSomeQuerying(Action<IQuery> queryThing);

mockedObject.Setup(m => m.DoSomeQuerying(It.Is<Action<IQuery>>(q => MyCheckingMethod(q)));

private bool MyCheckingMethod(Action<IQuery> queryAction)

    var mockQuery = new Mock<IQuery>();
    mockQuery.Setup(m => m.SetSomeFields(It.Is<string>(s => s.MeetsSomeCondition())
    queryAction.Invoke(mockQuery.Object);
    mockQuery.Verify(m => m.SetSomeFields(It.Is<string>(s => s.MeetsSomeCondition(), Times.Once)
    return true

【讨论】:

以上是关于使用 Moq 验证特定参数的主要内容,如果未能解决你的问题,请参考以下文章

使用 Moq 来验证调用是不是以正确的顺序进行

使用 Moq 模拟类时,如何仅针对特定方法进行 CallBase?

如何验证在 Moq 中没有调用该方法?

如何使用 Moq 为不同的参数设置两次方法

在 Moq 中分配 out/ref 参数

如何在没有明确指定或使用重载的情况下 Moq 在其签名中具有可选参数的方法?