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

How does NSubstitute .Returns<T>() work?

.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);

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

模拟方法的实际实现(非常粗略)类似于:

//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);
}

每当替代者收到呼叫时,它会记录有关呼叫的信息,并更新一些全局状态(线程本地,)记录它是最近调用的替代者。

.Returns 运行 时,它查找最后调用的替代,然后告诉替代它的最后调用应该存根到 return 该特定值。 (它还将它从已接电话的集合中删除,因此如果我们 运行 .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.

我认为这是对所发生情况的合理近似。如果您想查看代码,为了增加一点精确度,替代方法是 dynamic proxy which forwards every call to a "call router" which handles all the logic of the substitute (storing calls, configuring calls, adding callbacks etc.). The global state is a SubstitutionContext,它存储最后一个收到呼叫的呼叫路由器。

(Repo 链接到 v4.0.0-rc1 标签。以后的版本可能会更改,但总体思路应该保持相当一致。)