NSubstitute.Returns <t>()如何运作?

时间:2016-09-09 11:16:00

标签: c# mocking nsubstitute

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

2 个答案:

答案 0 :(得分:5)

每当替代者接收到一个呼叫时,它会记录有关该呼叫的信息,并更新一些全局状态(threadlocal,as Scott pointed out)记录它是最近的替代呼叫。

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

我认为这是对发生的事情的合理近似。要想要查看代码,要添加更多精度,替换为dynamic proxy forwards每次调用“call router”来处理替换的所有逻辑(存储呼叫,配置呼叫,添加回叫等)。全局状态是SubstitutionContext,它存储接收呼叫的最后一个呼叫路由器。

(回复链接指向v4.0.0-rc1标记。更高版本可能会发生变化,但整体想法应保持相当一致。)

答案 1 :(得分:3)

我相信它可以通过在调用模拟方法时在线程本地存储中保存上下文(称为ISubstitutionContext)来工作。然后对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);
}