我有一个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')
但是,它没有。或者也许我做错了什么?
答案 0 :(得分: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
我会考虑是否应该在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