使用Moq验证特定参数

Lui*_*bal 150 c# nunit unit-testing moq

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();
}
Run Code Online (Sandbox Code Playgroud)

我开始使用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());
}
Run Code Online (Sandbox Code Playgroud)

那么,我如何从Verify调用中提取表达式?

Ric*_*ebb 221

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

另一种选择是在Setup调用上使用回调来存储传递给mocked方法的值,然后编写标准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));
Run Code Online (Sandbox Code Playgroud)

  • 这种方法的一个很大的好处是,它将为您提供特定的测试失败,以确定对象是如何不正确的(当您单独测试每个对象时). (6认同)
  • 我认为按Mayo使用It.Is &lt;MyObject&gt;(validator)更好,因为它避免了将参数值另存为lambda的尴尬方式 (3认同)
  • 我以为我是唯一这样做的人,很高兴看到这是一种合理的方法! (2认同)
  • 这个线程安全吗,例如并行运行测试时? (2认同)
  • @AntonTolken我还没有尝试过,但在我的示例中,更新的对象是局部变量(saveObject),因此它应该是线程安全的。 (2认同)

May*_*ayo 91

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

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

如果你的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";
}
Run Code Online (Sandbox Code Playgroud)

另外,请注意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());
Run Code Online (Sandbox Code Playgroud)

同样的设置方法......

foreach (var item in myList) {
  var stuff = ... // some result specific to the item
  this.mockRepository
    .Setup(mr => mr.GetStuff(item.itemId))
    .Returns(stuff);
}
Run Code Online (Sandbox Code Playgroud)

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

this.mockRepository
    .Setup(mr => mr.GetStuff(It.IsAny<int>()))
    .Returns((int id) => SomeFunctionThatReturnsStuff(id));
Run Code Online (Sandbox Code Playgroud)

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


小智 20

一种更简单的方法是:

ObjectA.Verify(
    a => a.Execute(
        It.Is<Params>(p => p.Id == 7)
    )
);
Run Code Online (Sandbox Code Playgroud)