Returning the result of a method that returns another substitute throws an exception in NSubstitute

后端 未结 1 1086
自闭症患者
自闭症患者 2020-12-11 01:32

I have run into a weird issue while using NSubstitute a few times and although I know how to work around it I\'ve never been able to explain it.

I\'ve crafted what

相关标签:
1条回答
  • 2020-12-11 02:16

    To get the NSubstitute syntax to work there is some messiness going on behind the scenes. This is one of those cases where it bites us. Let's look at a modified version of your example first:

    sub.MyProperty.Returns(someValue);
    

    First, sub.MyProperty is called, which returns an IMyObject. The Returns extension method is then called, which needs to somehow work out which call it needs to return someValue for. To do this, NSubstitute records the last call it received in some global state somewhere. Returns in pseudo-ish-code looks something like this:

    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 evaluating the entire call looks a bit like this:

    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
    

    Now let's see what happens when we call another substitute while trying to set the return value:

    sub.MyProperty.Returns(MyMethod());
    

    Again this evaluates sub.MyProperty, then needs to evaluate Returns. Before it can do that, it needs to evaluate the arguments to Returns, which means running MyMethod(). This evaluation looks more like this:

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

    There is another example of the problems this can cause here.

    You can work around this by deferring the call to MyMethod(), by using:

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

    This works because MyMethod() will only execute when it needs to use a return value, so the static GetLastSubstituteCalled method doesn't get confused.

    Rather than doing that though, I prefer avoiding other calls to substitutes while I am busy configuring one.

    Hope this helps. :)

    0 讨论(0)
提交回复
热议问题