2013-05-20 7 views
10

NSubstituteを数回使用している間に奇妙な問題に遭遇しましたが、回避方法を知っていますが、別の代理を返すメソッドの結果を返すと、NSubstituteで例外がスローされます

私は問題を証明するために最低限必要なテストであると思われるものを作りました。これは、置換された戻り値を作成するメソッドを使用することと関連しているようです。

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

私は上記のテストを実行すると、私は次の例外を取得:しかし

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. 

を、私はこれを返すために試験方法を変更した場合:

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

またはこの:

var result = MyMethod(); 
sub.MyProperty.Returns(result); 

これがなぜ起こるのか誰かが説明できるかどうかは疑問です。

答えて

20

NS substitute構文を使用するには、背後で何か面倒なことがあります。これは、それが私たちを刺すそれらの場合の一つです。まずはあなたの例の修正版を見てみましょう:

sub.MyProperty.Returns(someValue); 

まず、sub.MyPropertyIMyObjectを返す、と呼ばれています。 Returnsという拡張メソッドが呼び出されます。これは何らかの理由でどの呼び出しを呼び出す必要がありますか?someValueを返します。これを行うために、NS代理は、どこかのグローバルな状態で受け取った最後の呼び出しを記録します。擬似っぽいコードでReturnsはこのようなものになります。

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 
} 

をだから、コール全体を評価するのは少し次のようになります。

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 

今の私たちがしようとしている間に別の代替を呼び出すときに何が起こるか見てみましょう戻り値を設定します。

sub.MyProperty.Returns(MyMethod()); 

は再び、これは sub.MyPropertyを評価し、その後、 Returnsを評価する必要があります。それを行うには、引数を Returnsに評価する必要があります。つまり、 MyMethod()を実行することを意味します。この評価は、より次のようになります。問題のもう一つの例があります

//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!* 

これはhereを引き起こす可能性があります。

あなたが使用することにより、MyMethod()への呼び出しを延期することによってこの問題を回避することができます

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

それは戻り値を使用する必要があるときにMyMethod()にのみ実行されるので、これは動作しますので、静的GetLastSubstituteCalled方法はありません混乱する。

しかし、そうするのではなく、代わりに他の呼び出しを避けることをお勧めします。

これが役に立ちます。 :)

+0

詳細な回答ありがとうございます。予想以上に複雑です。これはNHibernateのバグとみなされますか?私はあなたの答えによって、それは既知の問題であり、修正するのがむしろ複雑であると分かります。 – craftworkgames

+1

構文の選択には制限があります。 NSubを明示的な局所SubstituteFactoryを使用するように変更すると、テストごとに大量の状態を保持し、グローバルな状態ではなく検索することができますが、サブ作成の構文を複雑にしてもかまいません。私はそれを好むだろうと思うが、この時点で変更を正当化するのに十分な大きさの問題かどうかはわからない。 –

+0

実際の問題は、すべての角度からコードが正しいように見えるため、問題を診断するのが難しいことでした。おそらく、より良いエラーメッセージが役立つかもしれませんが、私は彼らが何であるか分かりません。再度、感謝します。 – craftworkgames

関連する問題