使用Moq验证特定参数


168
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();
}

我开始使用Moq并在挣扎中挣扎。我试图验证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());
}

顺便说一句,我如何从验证调用中提取表达式?

Answers:


250

如果验证逻辑很简单,那么编写大型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));

6
这种方法的一大好处是,它将为您提供有关对象如何错误的特定测试失败(当您分别测试每个对象时)。
Rob Church

1
我以为我是唯一这样做的人,很高兴看到这是一种合理的方法!
Apple将于

3
我认为按Mayo使用It.Is <MyObject>(validator)更好,因为它避免了将参数值保存为lambda的一部分的尴尬方式
stevec

例如并行运行测试时,此线程安全吗?
安东·托尔肯

@AntonTolken我没有尝试过,但是在我的示例中,更新的对象是局部变量(saveObject),因此它应该是线程安全的。
Rich Tebb

111

我一直在以相同的方式验证呼叫-我认为这是正确的方法。

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的一个错误,该错误消息指出该方法在根本没有被调用时被多次调用。他们可能已经修复了它-但是,如果您看到该消息,则可以考虑验证该方法是否已被实际调用。

编辑:这是为要验证您为列表中的每个对象调用函数的那些方案多次调用验证的示例(例如)。

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?)具有从某种出队对象返回的设置-每次调用该函数都会从队列中提取一个项目。


1
谢谢,这对我来说很有意义。我仍然不明白的是何时在设置或验证中指定详细信息。这很令人困惑。目前,我只是允许安装程序中的任何内容,并在验证中指定值。
路易斯·米拉巴尔

当有多个电话时,您认为我如何查看消息?客户端接收消息并可以创建多个queueableMessage,这些消息最终将在多个调用中出现,在每个调用中,我必须检查不同的消息。我总体上仍在努力进行单元测试,但我还不太熟悉。
路易斯·米拉巴尔

我不认为您应该如何做到这一点。它需要练习,您才能开始变得更好。对我来说,仅当我有一些要与之进行比较的参数以及尚未在另一项测试中测试该参数时,才指定参数。对于多个呼叫,有几种方法。为了设置和验证多次调用的函数,我通常为我希望的每个调用调用setup或verify(Times.Once())-通常带有for循环。您可以使用特定的参数来隔离每个呼叫。
Mayo

我为多次通话添加了一些示例-请参见上面的答案。
梅奥

1
“此外,请注意Mock的一个错误,该错误消息指出该方法在根本没有被调用时已多次调用。他们现在可能已对其进行了修复-但如果看到该消息,则可以考虑验证该方法。该方法实际上是被调用的。” -像这样的错误使模拟库恕我直言完全无效。讽刺的是,他们没有适当的测试代码:-)
Gianluca Ghettini

20

一个更简单的方法是:

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

我似乎无法正常工作,我正在尝试验证是否以Code.WRCC作为参数调用了我的方法。但是,我的测试总是通过,即使传递的参数为WRDD .. m.Setup(x => x.CreateSR(Code.WRDD)).ReturnsAsync(0); await ClassUnderTest.CompleteCC(accountKey, It.IsAny<int>(), mockRequest); m.Verify(x => x.CreateSR(It.Is<Code>(p => p == Code.WRCC)), Times.Once());
格雷格·奎因

1

我相信Moq会检查是否平等的问题。而且,由于XmlElement不会覆盖Equals,因此其实现将检查引用是否相等。

您不能使用自定义对象,因此可以覆盖equals吗?


是的,我最终做到了。我意识到问题出在检查Xml。在问题的第二部分中,我添加了一个可能的答案,即反序列化xml到对象
Luis Mirabal,

1

也有其中之一,但是操作的参数是没有公共属性的接口。最终将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
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.