Проверка определенного параметра с помощью Moq

170
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?

Луис Мирабал
источник

Ответы:

252

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

Другой вариант - использовать обратный вызов в вызове 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
Одним из больших преимуществ этого подхода является то, что он даст вам конкретные ошибки при тестировании на предмет некорректности объекта (так как вы тестируете каждый в отдельности).
Роб Черч
1
Я думал, что я был единственным, кто сделал это, рад видеть, что это разумный подход!
Уилл Эпплби
3
Я думаю, что лучше использовать It.Is <MyObject> (валидатор) в соответствии с Mayo, так как он избегает немного неуклюжего способа сохранения значения параметра как части лямбды
stevec
безопасен ли этот поток, например, при параллельном запуске тестов?
Антон Толкен
@AntonTolken Я не пробовал, но в моем примере обновляемый объект является локальной переменной (saveObject), поэтому он должен быть потокобезопасным.
Рич Тебб
113

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

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

РЕДАКТИРОВАТЬ: Вот пример вызова проверки несколько раз для тех сценариев, где вы хотите убедиться, что вы вызываете функцию для каждого объекта в списке (например).

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

майонез
источник
1
Спасибо, это имеет смысл для меня. То, что я до сих пор не могу понять, это когда указывать детали в настройках или проверки. Это довольно запутанно. На данный момент я просто разрешаю что-либо в программе установки и задаю значения в Verify.
Луис Мирабал
Как вы думаете, я могу проверить сообщения, когда есть несколько звонков? Клиент принимает сообщения и может создавать несколько сообщений queueableMessage, которые будут заканчиваться несколькими вызовами, и при каждом из этих вызовов мне приходится проверять разные сообщения. Я все еще борюсь с юнит-тестированием в целом, я еще не очень знаком с ним.
Луис Мирабал
Я не думаю, что есть волшебная серебряная пуля с точки зрения того, как вы должны это сделать. Это требует практики, и вы начинаете поправляться. Для меня я задаю параметры только тогда, когда у меня есть с чем сравнить, и когда я еще не тестирую этот параметр в другом тесте. Что касается нескольких звонков, есть несколько подходов. Для настройки и проверки функции, которая вызывается несколько раз, я обычно вызываю setup или verify (Times.Once ()) для каждого ожидаемого вызова - часто с циклом for. Вы можете использовать определенные параметры, чтобы изолировать каждый вызов.
Mayo
Я добавил несколько примеров для нескольких звонков - см. Ответ выше.
Майо,
1
«Кроме того, помните об ошибке в Mock, когда в сообщении об ошибке говорится, что метод вызывался несколько раз, когда он вообще не вызывался. Возможно, они уже исправили его - но если вы видите это сообщение, вы можете проверить его метод был на самом деле вызван ". - Подобная ошибка полностью делает недействительной библиотеку издевательств ИМХО. Как иронично, у них не было надлежащего кода для тестирования :-)
Джанлука Геттини
20

Более простым способом было бы сделать:

ObjectA.Verify(
    a => a.Execute(
        It.Is<Params>(p => p.Id == 7)
    )
);
dmitry.sergeyev
источник
Кажется, я не могу заставить это работать, я пытаюсь убедиться, что мой метод был вызван с 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, его реализация проверяет равенство ссылок.

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

Фернандо
источник
Да, я закончил этим. Я понял, что проблема была проверка Xml. Во второй части вопроса я добавил возможный ответ, десериализовавший xml к объекту
Луис Мирабал,
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
}
ds4940
источник