TL;DR
and_call_original
使う
allow(ENV).to receive(:[]).and_call_original
allow(ENV).to receive(:[]).with('HOGE').and_return('1')
これがシンプルな対応策
spec実行前にENVを上書き
参照 -> https://github.com/rspec/rspec-rails/issues/1279#issuecomment-70275896
そもそもENVをstubしなければいけない状況を改善する
def hoge?
ENV['HOGE'] == '1'
end
# または
config.hoge = ENV['HOGE']
として
#hoge
やconfig.hoge
をstubする
ENVをstubするときの問題点
# hoge.rb
def hoge
ENV['HUGA'] ||= 1
ENV['HOGE']
end
# hoge_spec.rb
before do
allow(ENV).to receive(:[]).with('HOGE').and_return('1')
end
という実装(すごく適当)になってたとき
Failure/Error: expect(instance.hoge).to eq '1'
ENV received :[] with unexpected arguments
expected: ("HOGE")
got: ("HUGA")
Please stub a default value first if message might be received with other args as well.
のようにspecが落ちる
stubしているENV['HOGE']
以外にENV.[]
にアクセスしているものがあるとspecが落ちてしまう。これが問題
今回の例ではENV['HUGA']
を意図的に呼ぶようにしてるのでいくらでも対処のしようはありそうだけど、
gemやフレームワークの中でENV.[]
を呼び出していることもるので対処はしづらい
もちろんENVに限った問題では無いのだけど、環境変数という性質上ENVがこの問題に陥りやすい
横道 ENV.fetch
について
ENV.fetch
のすすめ
詳しくは -> http://saiya-moebius.hatenablog.com/entry/2014/12/26/135041
[1] pry(main)> ENV['HOGE']
=> nil
[2] pry(main)> ENV.fetch('HOGE')
KeyError: key not found: "HOGE"
from (pry):2:in `fetch`
[3] pry(main)> ENV.fetch('HOGE', '1')
=> "1"
.[]
よりは.fecth
のほうが便利なことが多い
.[]
をstubするのは適切?
詳しくは -> https://github.com/rspec/rspec-rails/issues/1279#issuecomment-70275896
allow(ENV).to receive(:[]).with('HOGE').and_return('1')
では.[]
をstubしてるけど、specが実装に密接に関係しすぎてる
- ENV['HOGE']
+ ENV.fetch('HOGE')
と修正したらspecは落ちてしまう
.[]
が呼ばれていることをテストしたいわけではないはずなので、
この修正しただけ落ちてしまうspecはあまり良いspecではない
対応策
and_call_original
使う
allow(ENV).to receive(:[]).and_call_original
allow(ENV).to receive(:[]).with('HOGE').and_return('1')
一行追加するだけで済むしとてもシンプルな対応策
対応としては少しダサいが、ほとんど場合これでうまくいく
(.[]
をstubするのは適切?の問題は残ったままだけど)
spec実行前にENVを上書き
helperを用意する方法
詳しくは -> https://github.com/rspec/rspec-rails/issues/1279#issuecomment-70275896
(以下コピペ)
module EnvHelpers
def with_env_vars(vars)
original = ENV.to_hash
vars.each { |k, v| ENV[k] = v }
begin
yield
ensure
ENV.replace(original)
end
end
end
RSpec.configure do |c|
c.include EnvHelpers
end
it 'does something with the FOO environment variable' do
with_env_vars 'FOO' => 'bar' do
# logic that depends upon ENV['FOO'] goes here
end
end
すこし仰々しい気がするが、うまく動く(はず)
gemとして実装されているやつ(動作は未確認) -> https://github.com/ScrappyAcademy/rock_candy/blob/5695c7b231a44ef97a1be7293197130e8b553931/lib/rock_candy/helpers.rb
そもそもENVをstubしなければいけない状況を改善する
def hoge?
ENV['HOGE'] == '1'
end
# または
config.hoge = ENV['HOGE']
というような感じにして#hoge?
やconfig.hoge
をstubする
コードも読みやすくなるはずし、この対応ができるとベストだと思う