在RSpec中存根/模拟全局常量

Paw*_*cki 5 ruby rspec

我有一个gem,它有一个根据Rails.env采取不同行为的方法:

def self.env
  if defined?(Rails)
    Rails.env
  elsif ...
Run Code Online (Sandbox Code Playgroud)

现在我想编写一个测试代码路径的规范.目前我这样做:

Kernel.const_set(:Rails, nil)
Rails.should_receive(:env).and_return('production')
...
Run Code Online (Sandbox Code Playgroud)

没关系,只是觉得难看.另一种方法是声明spec_helper:

module Rails; end
Run Code Online (Sandbox Code Playgroud)

它也有效.但也许有更好的方法?理想情况下,这应该工作:

rails = double('Rails')
rails.should_receive(:env).and_return('production')
Run Code Online (Sandbox Code Playgroud)

但是,它没有.或者也许我做错了什么?

Dav*_*sky 9

根据有关这方面的各种推文,开启常量通常是一个坏主意,因为它使测试成为一个挑战,你必须改变常量的状态才能这样做(这使它们比常量少一点) .这就是说,如果你正在写有不同的行为,这取决于它的加载环境的一个插件,你将要测试的存在Rails,Merb等来的地方,即使它不是在这个特殊的部分的代码.无论在哪里,您都希望将其隔离,以便决策只发生一次.有点像MyPlugin::env.现在,您可以在大多数地方安全地存根该方法,然后通过存根常量来规范该方法.

至于如何存根常量,你的例子看起来不太正确.代码询问是否defined?(Rails),但是Kernel.const_set(:Rails, nil)没有取消定义常量,它只是将其值设置为nil.你想要的是这样的(免责声明 - 这是我的头脑,未经测试,甚至没有运行,可能包含语法错误,并没有很好的因素):

def without_const(const)
  if Object.const_defined?(const)
    begin
      @const = const
      Object.send(:remove_const, const)
      yield
    ensure
      Object.const_set(const, @const)
    end
  else
    yield
  end
end

def with_stub_const(const, value)
  if Object.const_defined?(const)
    begin
      @const = const
      Object.const_set(const, value)
      yield
    ensure
      Object.const_set(const, @const)
    end
  else
    begin
      Object.const_set(const, value)
      yield
    ensure
      Object.send(:remove_const, const)
    end
  end
end

describe "..." do
  it "does x if Rails is defined" do
    rails = double('Rails', :env => {:stuff_i => 'need'})
    with_stub_const(:Rails, rails) do
      # ...
    end
  end

  it "does y if Rails is not defined" do
    without_const(:Rails) do
      # ....
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

我会考虑是否应该在rspec中包含这个.如果我们添加人员会将它作为依赖常量的借口而不需要:)这是其中之一

  • 从Rspec 2.11开始,你现在可以`stub_const("FOO",:bar)` (3认同)