我有一个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)
但是,它没有.或者也许我做错了什么?
根据有关这方面的各种推文,开启常量通常是一个坏主意,因为它使测试成为一个挑战,你必须改变常量的状态才能这样做(这使它们比常量少一点) .这就是说,如果你正在写有不同的行为,这取决于它的加载环境的一个插件,你将要测试的存在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中包含这个.如果我们添加人员会将它作为依赖常量的借口而不需要:)这是其中之一