Перевірка конкретного параметра за допомогою 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?

Відповіді:


251

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

Іншим варіантом є використання зворотного дзвінка під час виклику 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
Я думав, що я єдиний, хто це робив, радий, що це розумний підхід!
Буде Appleby

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

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

Ще один метод, який я бачив у своєму блозі деякий час назад (можливо, Філ Хаак?), Мав налаштування, повертаючись з якогось об'єкта видалення - кожен раз, коли функція називалася, вона витягувала елемент з черги.


1
Дякую, для мене це має сенс. Що я досі не можу зрозуміти, це коли вказати деталі в Налаштування чи Перевірити. Це досить заплутано. На даний момент я просто дозволяю що-небудь у програмі установки та вказую значення в Verify.
Луїс Мірабал

Як ви думаєте, як я можу перевірити повідомлення, коли є кілька дзвінків? Клієнт приймає повідомлення та може створювати кілька повідомлень, що містяться в черзі, які в кінцевому підсумку перетворюються на кілька дзвінків, і в кожному з цих дзвінків я повинен перевіряти різні повідомлення. Я все ще борюся з одиничним тестуванням загалом, я ще не дуже знайомий з ним.
Луїс Мірабал

Я не думаю, що є чарівна срібна куля з точки зору того, як ви повинні це зробити. Це вимагає практики, і ви починаєте удосконалюватися. Для мене я вказую параметри лише тоді, коли мені є з чим порівняти їх і коли я вже не тестую цей параметр в іншому тесті. Щодо декількох дзвінків, то існує кілька підходів. Для налаштування та перевірки функції, яка викликається кілька разів, я зазвичай викликаю налаштування або підтверджую (Times.Once ()) для кожного дзвінка, який я очікую - часто з циклом for. Ви можете використовувати конкретні параметри, щоб ізолювати кожен виклик.
травня

Я додав кілька прикладів для кількох дзвінків - див. Відповідь вище.
травня

1
"Крім того, будьте в курсі помилки з Mock, де повідомлення про помилку вказує, що метод викликався кілька разів, коли він взагалі не викликався. Вони, можливо, це вже виправили - але якщо ви побачите це повідомлення, ви можете розглянути можливість підтвердження цього метод насправді називався ". - Така помилка повністю приводить в недійсність насмішку бібліотеку IMHO. Як іронічно вони не мали для цього належного коду тестування :-)
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 не перекриває рівність, його реалізація перевірятиме рівність еталонів.

Не можете використовувати спеціальний об'єкт, щоб ви могли переокремити рівні?


Так, я закінчила це робити. Я зрозумів, що проблема перевіряє 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
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.