2013-05-20 12 views
10

Ich habe ein seltsames Problem bei der Verwendung von NSubstitute ein paar Mal gerannt und obwohl ich weiß, wie man es umgehen kann, konnte ich es nie erklären.Rückgabe des Ergebnisses einer Methode, die einen anderen Ersatz zurückgibt, löst eine Ausnahme in NSubstitute aus

Ich habe den minimal erforderlichen Test erstellt, um das Problem zu beweisen, und es scheint etwas mit der Verwendung einer Methode zum Erstellen eines ersetzten Rückgabewerts zu tun zu haben.

public interface IMyObject 
{ 
    int Value { get; } 
} 

public interface IMyInterface 
{ 
    IMyObject MyProperty { get; } 
} 

[TestMethod] 
public void NSubstitute_ReturnsFromMethod_Test() 
{ 
    var sub = Substitute.For<IMyInterface>(); 

    sub.MyProperty.Returns(MyMethod()); 
} 

private IMyObject MyMethod() 
{ 
    var ob = Substitute.For<IMyObject>(); 
    ob.Value.Returns(1); 
    return ob; 
} 

Wenn ich laufe den obigen Test ich die folgende Ausnahme erhalten:

Test method globalroam.Model.NEM.Test.ViewModel.DelayedAction_Test.NSubstitute_ReturnsFromMethod_Test threw exception: 
NSubstitute.Exceptions.CouldNotSetReturnException: Could not find a call to return from. 
Make sure you called Returns() after calling your substitute (for example: mySub.SomeMethod().Returns(value)). 
If you substituted for a class rather than an interface, check that the call to your substitute was on a virtual/abstract member. 
Return values cannot be configured for non-virtual/non-abstract members. 

Allerdings, wenn ich die Testmethode ändern, um dies zurück:

sub.MyProperty.Returns((a) => MyMethod()); 

oder dieses:

Es funktioniert .

Ich frage mich nur, ob jemand erklären kann, warum das passiert?

Antwort

20

Um die NSubstitute-Syntax zum Laufen zu bringen, gibt es hinter den Kulissen etwas Unordnung. Dies ist einer jener Fälle, in denen es uns beißt. Lassen Sie uns auf einer modifizierten Version von Ihrem Beispiel zunächst einen Blick:

sub.MyProperty.Returns(someValue); 

Zuerst sub.MyProperty genannt wird, die eine IMyObject zurückgibt. Dann wird die Extension-Methode Returns aufgerufen, die irgendwie herausfinden muss, welchen Aufruf sie für someValue zurückgeben muss. Um dies zu tun, nimmt NSubstitute den letzten Ruf auf, den er irgendwo in einem globalen Zustand erhalten hat. Returns in pseudo-ish-Code sieht wie folgt aus etwas:

public static void Returns<T>(this T t, T valueToReturn) { 
    var lastSubstitute = bigGlobOfStaticState.GetLastSubstituteCalled(); 
    lastSubstitute.SetReturnValueForLastCall(valueToReturn); 
    bigGlobOfStaticState.ClearLastCall(); // have handled last call now, clear static state 
} 

So den gesamten Anruf Auswertung sieht ein bisschen wie folgt aus:

sub.MyProperty   // <-- last call is sub.MyProperty 
    .Returns(someValue) // <-- make sub.MyProperty return someValue and 
         //  clear last call, as we have already set 
         //  a result for it 

Nun wollen wir sehen, was passiert, wenn wir einen anderen Ersatz nennen, während zu versuchen, stellen Sie den Rückgabewert:

sub.MyProperty.Returns(MyMethod()); 

Auch in diesem sub.MyProperty auswertet, dann Returns bewerten muss. Bevor dies möglich ist, müssen die Argumente für Returns ausgewertet werden, was bedeutet, dass MyMethod() ausgeführt wird. Diese Auswertung sieht mehr wie folgt aus:

//Evaluated as: 
sub.MyProperty  // <- last call is to sub.MyProperty, as before 
    .Returns(
    // Now evaluate arguments to Returns: 
    MyMethod() 
     var ob = Substitute.For<IMyObject>() 
     ob.Value  // <- last call is now to ob.Value, not sub.MyProperty! 
     .Returns(1) // <- ok, ob.Value now returns 1, and we have used up the last call 
    //Now finish evaluating origin Returns: 
    GetLastSubstituteCalled *ugh, can't find one, crash!* 

Es gibt ein anderes Beispiel für die Probleme ist dies here verursachen.

Sie können dieses Problem umgehen, indem Sie den Aufruf an MyMethod() aufzuschieben, unter Verwendung von:

sub.MyProperty.Returns(x => MyMethod()); 

Das funktioniert, weil MyMethod() wird nur ausgeführt werden, wenn es einen Rückgabewert verwenden muss, so dass die statische GetLastSubstituteCalled Methode nicht verwirrt werden.

Anstatt dies zu tun, bevorzuge ich jedoch andere Anrufe zu substituieren, während ich damit beschäftigt bin, einen zu konfigurieren.

Hoffe, das hilft. :)

+0

Danke für die ausführliche Antwort. Es ist viel komplizierter als ich erwartet habe. Denken Sie, dass dies ein Fehler in NHibernate ist? Ich nehme an, dass es sich um ein bekanntes Problem handelt, das ziemlich komplex zu beheben ist. – craftworkgames

+1

Es ist eine Einschränkung der Wahl der Syntax. Die Änderung von NSub, um explizite, lokale SubstituteFactories zu verwenden, könnte helfen (wir könnten eine Menge an Status pro Test beibehalten und durchsuchen, anstatt globalen Status), aber nicht sicher, ob die Leute bereit wären, die Syntax zum Erstellen von Subs zu komplizieren. Ich denke, mir wäre das lieber, aber ich bin mir nicht sicher, ob es ein Problem ist, das groß genug ist, um Veränderungen an dieser Stelle zu rechtfertigen. –

+0

Das eigentliche Problem ist, dass der Code aus jedem Blickwinkel korrekt war, so dass es schwierig war, das Problem zu diagnostizieren. Vielleicht helfen bessere Fehlermeldungen, aber ich habe keine Ahnung, was sie sein könnten. Danke noch einmal. – craftworkgames