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

我在使用NSubstitute几次时遇到了一个奇怪的问题,虽然我知道如何解决它,但我从来没有能够解释它。

我已经精心设计了certificate问题的最低要求测试,它似乎与使用方法创建替代返回值有关。

public interface IMyObject { int Value { get; } } public interface IMyInterface { IMyObject MyProperty { get; } } [TestMethod] public void NSubstitute_ReturnsFromMethod_Test() { var sub = Substitute.For(); sub.MyProperty.Returns(MyMethod()); } private IMyObject MyMethod() { var ob = Substitute.For(); ob.Value.Returns(1); return ob; } 

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

 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语法起作用,幕后会出现一些混乱。 这是它咬我们的案例之一。 让我们先看看你的例子的修改版本:

 sub.MyProperty.Returns(someValue); 

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

 public static void Returns(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() 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方法不会混淆。

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

希望这可以帮助。 🙂