NSubstitute .Returns<T>() 是如何工作的?

6

.Returns<T> (this T value, ... ) 扩展方法是如何工作的呢?

具体来说,.Returns 是如何仅通过执行该方法的结果就知道它要配置哪个方法的呢?

例如:

public interface ICalculator { Add(int a, int b); }

// create mock
var calculator = Substitute.For<ICalculator>();

// How does this piece work under the hood?
calculator.Add(1, 2).Returns(3);

https://github.com/nsubstitute/NSubstitute/blob/master/Source/NSubstitute/Extensions/ReturnsForAllExtensions.cs - Sebastian Siemens
2个回答

6
每当替代对象接收到一个调用时,它会记录有关调用的信息,并更新一些全局状态(线程本地,正如Scott所指出的),记录它是最近被调用的替代对象。
.Returns 运行时,它查找最后一个被调用的替代对象,然后告诉替代对象其最后一个调用应该被存根以返回该特定值。(它还从接收到的调用集合中删除它,因此如果我们运行 .Received() ,存根调用不会被误认为是真实的调用。)
calculator
    .Add(1, 2)   // substitute records Add(1,2) called. Last substitute
                 // set to `calculator`. Returns default `int` in this case.
    .Returns(3)  // Looks up last sub, sets its last call to return 3.

我认为这是发生的事情的合理近似。如果您想查看代码,替代品是动态代理,它转发每个调用到一个“调用路由器”,该路由器处理所有替代逻辑(存储调用、配置调用、添加回调等)。全局状态是{{link4:SubstitutionContext}},它存储接收到调用的最后一个调用路由器。 (Repo链接指向v4.0.0-rc1标记。稍后的版本可能会更改,但总体思想应保持相对一致。)

4

我相信它的工作原理是在调用模拟方法时,将上下文(称为ISubstitutionContext)保存在线程本地存储中。然后,Returns调用获取此上下文并在返回对象中设置适当的数据。

模拟方法的实际实现可能(非常粗略地)如下所示:

//Dynamically created mock
public int Add(int a, int b)
{
    var context = new SubstitutionContext("Add", ...);

    //CallContext.LogicalSetData or
    //ThreadStatic or
    //ThreadLocal<T> or
    //...

    return 0;
}

//In some extension class
public static ConfiguredCall Returns<T>(this T value, ...)
{
    var context = SubstitutionContext.Current; //Gets from thread local storage
    return context.LastCallShouldReturn(value);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接