将类/模块恢复到处女状态

时间:2015-09-16 11:43:51

标签: ruby rspec

我想知道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无论如何。

1 个答案:

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