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

时间:2011-10-21 10:14:35

标签: ruby rspec

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

def self.env
  if defined?(Rails)
    Rails.env
  elsif ...

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

Kernel.const_set(:Rails, nil)
Rails.should_receive(:env).and_return('production')
...

没关系,只觉得难看。另一种方法是在spec_helper

中声明这一点
module Rails; end

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

rails = double('Rails')
rails.should_receive(:env).and_return('production')

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

4 个答案:

答案 0 :(得分:9)

根据各种关于此的推文,开启常量通常是一个坏主意,因为它会使测试成为一个挑战,你必须改变常量的状态才能这样做(这使它们略微不变)。也就是说,如果您编写的插件必须根据其加载的环境而有不同的行为,那么您将不得不测试RailsMerb等的存在。在某个地方,即使它不在代码的这个特定部分。无论在哪里,您都希望将其隔离,以便决策只发生一次。像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

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

答案 1 :(得分:1)

测试定义Rails的条件

 mock_rails = mock(:env => mock)
 Kernel.stub(:Rails).and_return(mock_rails)

要测试Rails未定义的条件,我相信你不应该做任何事情,否则你会定义Rails然后if if defined?(Rails)条件会再次成立

答案 2 :(得分:0)

David's answer帮助了我很多。我修改了一点来处理多个常量:

# Mock a constant within the passed block
# @example mock RAILS_ENV constant
#   it "does not allow links to be added in production environment" do
#     with_constants :RAILS_ENV => 'production' do
#       get :add, @nonexistent_link.url
#       response.should_not be_success
#     end
#   end
# @note adapted from:
#   * https://stackoverflow.com/a/7849835/457819
#   * http://digitaldumptruck.jotabout.com/?p=551
def with_constants(constants)
  @constants_to_restore = {}
  @constants_to_unset   = []

  constants.each do |name, val|
    if Object.const_defined?(name)
      @constants_to_restore[name] = Object.const_get(name)
    else
      @constants_to_unset << name
    end

    Object.const_set( name, val )
  end

  begin
    yield
  ensure
    @constants_to_restore.each do |name, val|
      Object.const_set( name, val )
    end

    @constants_to_unset.each do |name|
      Object.send(:remove_const, name)
    end
  end
end

def without_constants(constants)
  @constants_to_restore = {}

  constants.each do |name, val|
    if Object.const_defined?(name)
      @constants_to_restore[name] = Object.const_get(name)
    end

    Object.send(:remove_const, name)
  end

  begin
    yield
  ensure
    @constants_to_restore.each do |name, val|
      Object.const_set( name, val )
    end
  end
end

答案 3 :(得分:0)

在RSpec中存根全局常量的现代方法涉及使用stub_const。假设您有一个使用Rails的方法,您希望在未定义Rails的环境中进行测试(例如rubygem):

def MyClass
  def cache_value key, value
    Rails.cache.write key, value
  end
end

您可以像这样编写此方法的规范:

it 'writes the value to the cache' do
  key = :key
  value = 'abc'

  cache = double 'cache'
  rails = double 'Rails', cache: cache

  stub_const('Rails', rails)

  expect(cache).to receive(:write).with(key, value)

  MyClass.new.cache_value key, value
end