Arg.Do() не срабатывает, когда ожидается в методе When..Do for void

У меня есть приведенная ниже структура в моем тесте, предназначенном для проверки того, что определенный журнал вызывается с правильным сложным объектом аргумента, даже когда он генерирует исключение, которое затем обертывается и обычно обрабатывается дальше. У logThing есть метод:

void AddEntry(LogEntry);

Итак, я использую When..Do, чтобы вызвать исключение,

public void UnitTest()
{
    // Arrange
    ILogThing logThing = Substitute.For<ILogThing>()
    SystemUnderTest system = new SystemUnderTest(logThing);

    List<LogEntry> actualEntries = new List<LogEntry>();
    LogEntry expectedEntry = GetSomeTestData();

    logThing.When(
        lt => lt.AddEntry(Arg.Do<LogEntry>(r => actualEntries.Add(r)))).Do(
        call => { throw new InvalidOperationException("testMessage"); });

    // Act
    try
    {
        system.DoSomethingWhichLogs(someArgs)
    }
    catch(WrappedException ex)
    {
        // Assert
        Assert.AreEqual(1, actualEntries.Count);
        Assert.AreEqual(actualEntries[0], expectedEntry);
    }
}

Однако при такой настройке ожидаемый вызов Arg.Do() никогда не происходит.

Я поставил точку останова в блоке catch и использовал непосредственное окно Visual Studio для вызова RecievedCalls‹>() в logThing, и у него есть запись об одном вызове logThing с правильными аргументами — это просто Arg.Do кажется, выполняется только после завершения блока When..Do. Ясно, что это означает, что, поскольку я добавляю When..Do, он никогда не достигает этого.

Я действительно не ожидал, что NSubstitute будет упорядочивать вызовы таким образом, это ожидаемое поведение? Если да, могу ли я что-нибудь сделать, чтобы проверить входящий аргумент, подобный этому, или я должен просто поместить проверку аргумента в основной блок When..Do (что затрудняет чтение)?

Тестируемая система выполняет различные действия с исключением, в том числе оборачивает его вместе с logEntry, поэтому мне полезно иметь все эти проверки в одном тесте - я думал разделить его на два отдельных теста, но понял что, если бы я сделал это, я не мог бы легко определить, откуда исходит неправильный обернутый вывод (это может быть либо часть, которая изначально генерирует logEntry, либо часть, обертывающая его), тогда как с этим шаблоном я могу проверить, чтобы убедиться logThing получает то, что я ожидаю. Тем не менее, если есть лучший способ сделать это, я, безусловно, открыт для предложений.


person Ben    schedule 28.09.2011    source источник


Ответы (1)


Точный порядок When..Do и Arg.Do не определен, и я бы не рекомендовал рассчитывать на него, так как я полагаю, что он может меняться между версиями в зависимости от реализации. (Если у вас есть веская причина определить его в определенном порядке, опубликуйте свое предложение в группе пользователей. .)

Если вы просто хотите проверить, что logThing получил ожидаемый LogEntry, вы можете проверить аргумент постфактум, используя Arg.Is():

logThing.Received().AddEntry(Arg.Is(expectedEntry));

Если вам нужна более сложная логика сравнения, вы можете сделать то же самое, но использовать метод для проверки аргумента:

logThing.Received().AddEntry(Arg.Is<LogEntry>(x => CheckLogEntry(x)));

CheckLogEntry может выполнять любые проверки, которые вам требуются, и вы можете оставить бросок When..Do, как и раньше.

Если вам нужно использовать подход When..Do, вы можете сохранить свой существующий подход, но переместить захват аргумента в вызов Do, чтобы обеспечить ожидаемый порядок вызовов:

logThing.When(
    lt => lt.AddEntry(Arg.Any<LogEntry>())).Do(
    call =>
    {
        actualEntries.Add(call.Arg<LogEntry>());
        throw new InvalidOperationException("testMessage");
    });

Что касается предложений по другим способам тестирования, я не совсем понимаю, что вы пытаетесь здесь сделать, но если у вас возникли проблемы с идентификацией того, откуда исходит обернутый вывод, возможно, вы могли бы перенести эту ответственность на другую зависимость? Это позволит вам заменить эту зависимость и контролировать, где именно это произошло с точки зрения этого теста.

person David Tchepak    schedule 29.09.2011
comment
Спасибо за разъяснение по поводу заказа (или его отсутствия). У меня нет веских причин желать этого таким образом, просто я ожидал, что это произойдет «до» функции, для которой используются аргументы. Первоначально у меня было logThing.Received().AddEntry(Arg.Do‹LogEntry›(x => CheckLogEntry(x))); но это никогда не срабатывало. На самом деле я не думал об использовании .Is, но я сделаю это, возвращая логическое значение после проверок, я думаю, что это самый аккуратный способ. - person Ben; 29.09.2011
comment
Да, Arg.Do не сработает в Received(), так как Received() проверяет вызовы постфактум. Arg.Do срабатывает только для будущих вызовов, которые получены. - person David Tchepak; 29.09.2011
comment
Знаете ли вы, есть ли способ предотвратить .Is() от проглатывания исключений? Это делает чтение утверждения из сообщения об ошибке практически невозможным, поскольку текущая ошибка, выдаваемая Is, говорит только о том, что сложный объект не соответствует, она не указывает, где и как. - person Ben; 29.09.2011
comment
Нет, ты не можешь перестать глотать исключения. Я бы не стал утверждать из метода, который вы передаете Is, просто вернул бы true или false в зависимости от того, совпадает ли он. В настоящее время мы работаем над реализацией пользовательских сопоставителей, которые предоставят больше информации, но эта работа еще не реализована в последних сборках. - person David Tchepak; 29.09.2011
comment
На данный момент я вернулся к захвату аргумента - мне нужно знать, где в объекте проблема. Тогда я обязательно продолжу проверять более поздние сборки. - person Ben; 29.09.2011
comment
Звучит неплохо. Приносим извинения за доставленные неудобства. Сборки находятся на сервере CI, на который есть ссылка на веб-сайте, а выпуски объявляются в группе пользователей (ссылка на мой ответ), а также в Twitter (twitter.com/nsubstitute), если список слишком много болтает. - person David Tchepak; 29.09.2011