返回返回另一个替代品的方法的结果会在NSubstitute中抛出异常

本文关键字:返回 NSubstitute 抛出异常 结果 另一个 替代品 方法 | 更新日期: 2023-09-27 18:14:18

我遇到了一个奇怪的问题,而使用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);

我只是想知道是否有人能解释为什么会发生这种情况?

返回返回另一个替代品的方法的结果会在NSubstitute中抛出异常

为了使NSubstitute语法正常工作,在幕后发生了一些混乱的事情。这是其中一个被它咬伤的案例。让我们先看一下这个示例的修改版本:

sub.MyProperty.Returns(someValue);

首先调用sub.MyProperty,它返回一个IMyObject。然后调用Returns扩展方法,它需要以某种方式计算出需要为哪个调用返回someValue。为此,NSubstitute记录它在某个全局状态下收到的最后一次调用。伪代码中的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!*

这里有另一个可能引起问题的例子。

您可以通过使用以下命令延迟对MyMethod()的调用来解决这个问题:

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

这可以工作,因为MyMethod()只在需要使用返回值时执行,所以静态GetLastSubstituteCalled方法不会混淆。

与其这样做,我更喜欢在我忙于配置一个替代品时避免其他调用。

希望这对你有帮助。:)