【问题标题】:How does NSubstitute .Returns<T>() work?NSubstitute .Returns<T>() 如何工作?
【发布时间】:2016-09-09 11:15:59
【问题描述】:
.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);
【问题讨论】:
标签:
c#
mocking
nsubstitute
【解决方案1】:
每当替代品收到呼叫时,它都会记录有关该呼叫的信息,并更新一些全局状态(线程本地,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标签。以后的版本可能会改变,但总体思路应该保持相当一致。)
【解决方案2】:
我相信,当调用模拟方法时,它可以通过在线程本地存储中保存上下文(称为 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);
}