2015-09-16 42 views
4

我想知道RSpec的是如何实现这一点:还原类/模块的处女状态

是否RSpec的利用define_singleton_method注入:the_answerMyObject

这可以工作。让我们假装这是MyObject样子:

class MyObject 
    def self.the_answer 
     21 * 2 
    end 
end 

注入the_answer方法可能包含这样的事情:

@the_answer_called = true 
42 

然而,如何RSpec的那么类恢复到unmocked状态?它如何恢复它,以便MyObject.the_answer“真正”再次返回42? (通过21 * 2

由于写这个问题,我认为它没有。在RSpec停止运行之前,类方法仍会被模拟。

但是,(这是问题的症结所在)如何撤销define_singleton_method?

我想会运行old_object = Object.dupdefine_singleton_method之前使用的最简单的方法,但后来,我怎么恢复old_objectObject

当我想要做的就是恢复一个类的方法时,这也不会使我高效。虽然目前这不是问题,但也许在将来我还想保持MyObject内的某些实例变量不变。是否有非哈希方式复制代码(21 * 2)并重新定义为the_answer通过define_singleton_method而不是完全取代Object

所有的想法欢迎,我绝对想知道如何使Object === old_object不管。

+1

您的示例类显示了一个实例方法,而'expect'行和您的大部分问题都是关于类方法的。 –

+1

Neil Slater Thankyou,我纠正了(我认为) – JayTarka

回答

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的实际代码要复杂得多,但你明白了。

+0

非常感谢!如果'the_answer'是一个类方法,我相信你可以使用'<#Object> .method'到'<#Object> .instance_method'来重复这些步骤?在我的例子中,我错过了'self',道歉:P – JayTarka

+1

@JayTarka如果用MyObject.singleton_class替换MyObject,我的例子应该用于类方法。 'MyObject.singleton_class.instance_method(:the_answer)'。 – Stefan