Проверка определенного параметра с помощью 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();
}

Я начинаю использовать 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());
}

Кстати, как я мог извлечь выражение из вызова Verify?

Ответ 1

Если логика верификации нетривиальна, будет беспорядочно писать большой лямбда-метод (как показывает ваш пример). Вы можете поместить все тестовые операторы в отдельный метод, но я не люблю это делать, потому что это нарушает поток чтения тестового кода.

Другим вариантом является использование обратного вызова в вызове 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));

Ответ 2

Я проверяю вызовы одинаково - я считаю, что это правильный способ сделать это.

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

Если ваше лямбда-выражение становится громоздким, вы можете создать функцию, которая принимает 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, где в сообщении об ошибке указано, что метод вызывается несколько раз, когда он вообще не вызывался. Возможно, они уже исправили его, но если вы увидите это сообщение, вы можете проверить, действительно ли этот метод был вызван.

EDIT: Вот пример вызова проверки несколько раз для тех сценариев, где вы хотите проверить, что вы вызываете функцию для каждого объекта в списке (например).

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

Поэтому каждый раз, когда GetStuff вызывается для этого элемента itemId, он возвращает материал, специфичный для этого элемента. В качестве альтернативы вы можете использовать функцию, которая принимает itemId в качестве входных данных и возвращает материал.

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

Еще один метод, который я видел в блоге некоторое время назад (возможно, Фил Хаак?), возвращался с какого-то объекта dequeue - каждый раз, когда вызывалась функция, он вытягивал элемент из очереди.

Ответ 3

Более простой способ:

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

Ответ 4

Я считаю, что проблема в том, что Moq проверит равенство. И поскольку XmlElement не переопределяет Equals, реализация будет проверять ссылочное равенство.

Нельзя ли использовать пользовательский объект, чтобы вы могли переопределить равные?