我想知道RSpec如何实现这个目标:
expect( MyObject ).to receive(:the_answer).and_return(42)
RSpec是否利用define_singleton_method
将:the_answer
注入MyObject
?
这可行。让我假装这是MyObject
的样子:
class MyObject
def self.the_answer
21 * 2
end
end
注入的 the_answer
方法可能包含以下内容:
@the_answer_called = true
42
然而,RSpec如何将类恢复到其未模仿状态?如何恢复它以便MyObject.the_answer
“真正”再次返回42? (通过21 * 2
)
自写这个问题以来,我认为它没有。在RSpec停止运行之前,类方法仍然是模拟的。
但是,(这是问题的症结所在)如何撤消define_singleton_method?
我认为在使用old_object = Object.dup
之前最简单的方法是运行define_singleton_method
,但是,如何将old_object
恢复为Object
?
当我想要做的就是恢复一个类方法时,这也不会让我觉得有效。虽然目前这不是一个问题,但也许将来我也希望保持MyObject
内的某些实例变量不变。是否有非hacky 方式复制代码(21 * 2
)并通过the_answer
将其重新定义为define_singleton_method
而不是完全替换Object
?
欢迎所有想法,我绝对想知道如何让Object
=== old_object
无论如何。
答案 0 :(得分:3)
RSpec不会复制/替换实例或其类。它动态删除实例方法并定义一个新方法。以下是它的工作原理:(你可以对类方法做同样的事情)
鉴于您的课程MyObject
和实例o
:
class MyObject
def the_answer
21 * 2
end
end
o = MyObject.new
o.the_answer #=> 42
RSpec首先使用Module#instance_method
保存原始方法。它返回UnboundMethod
:
original_method = MyObject.instance_method(:the_answer)
#=> #<UnboundMethod: MyObject#the_answer>
然后使用Module#remove_method
删除该方法:(我们必须在此使用send
,因为remove_method
是私有的。)
MyObject.send(:remove_method, :the_answer)
并使用Module#define_method
定义一个新的:
MyObject.send(:define_method, :the_answer) { 'foo' }
如果您现在致电the_answer
,您将立即调用新方法:
o.the_answer #=> "foo"
在示例之后,RSpec删除了新方法
MyObject.send(:remove_method, :the_answer)
并恢复原始版本(define_method
接受块或方法):
MyObject.send(:define_method, :the_answer, original_method)
调用方法按预期工作:
o.the_answer #=> 42
RSpec的实际代码要复杂得多,但你明白了。