返回返回另一个替换的方法的结果会在NSubstitute中引发异常

cra*_*mes 11 c# 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;
}
Run Code Online (Sandbox Code Playgroud)

当我运行上面的测试时,我得到以下异常:

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.
Run Code Online (Sandbox Code Playgroud)

但是,如果我更改测试方法以返回此:

sub.MyProperty.Returns((a) => MyMethod());
Run Code Online (Sandbox Code Playgroud)

或这个:

var result = MyMethod();
sub.MyProperty.Returns(result);
Run Code Online (Sandbox Code Playgroud)

有用.

我只是想知道是否有人能解释为什么会这样?

Dav*_*pak 21

为了使NSubstitute语法起作用,幕后会出现一些混乱.这是它咬我们的案例之一.让我们先看看你的例子的修改版本:

sub.MyProperty.Returns(someValue);
Run Code Online (Sandbox Code Playgroud)

首先,sub.MyProperty被调用,返回一个IMyObject.在Returns随后扩展方法被调用,它需要以某种方式寻找哪些调用它需要返回someValue的.为此,NSubstitute记录它在某个全局状态下收到的最后一次调用.Returns在伪ish代码看起来像这样:

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
}
Run Code Online (Sandbox Code Playgroud)

所以评估整个调用看起来有点像这样:

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
Run Code Online (Sandbox Code Playgroud)

现在让我们看看当我们在尝试设置返回值时调用另一个替换时会发生什么:

sub.MyProperty.Returns(MyMethod());
Run Code Online (Sandbox Code Playgroud)

再次评估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!*
Run Code Online (Sandbox Code Playgroud)

还有就是这可能会导致问题的另一个例子在这里.

您可以MyMethod()通过使用以下方式将呼叫推迟到以下方法来解决此问题:

sub.MyProperty.Returns(x => MyMethod());
Run Code Online (Sandbox Code Playgroud)

这样做是因为MyMethod()只有在需要使用返回值时才会执行,因此静态GetLastSubstituteCalled方法不会混淆.

虽然不是这样做,但我更喜欢在忙于配置替换时避免其他替换呼叫.

希望这可以帮助.:)