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, но ще го направя, като връща bool след проверките, мисля, че това е най-добрият начин. - 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