LoginSignup
69
48

More than 5 years have passed since last update.

RspecでENVをどうstubするのがよいのか

Last updated at Posted at 2015-10-11

TL;DR

:one: and_call_original使う

allow(ENV).to receive(:[]).and_call_original
allow(ENV).to receive(:[]).with('HOGE').and_return('1')

これがシンプルな対応策

:two: spec実行前にENVを上書き

参照 -> https://github.com/rspec/rspec-rails/issues/1279#issuecomment-70275896

:three: そもそもENVをstubしなければいけない状況を改善する

def hoge?
  ENV['HOGE'] == '1'
end
# または
config.hoge = ENV['HOGE']

として
#hogeconfig.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ではない

対応策

:one: and_call_original使う

allow(ENV).to receive(:[]).and_call_original
allow(ENV).to receive(:[]).with('HOGE').and_return('1')

一行追加するだけで済むしとてもシンプルな対応策
対応としては少しダサいが、ほとんど場合これでうまくいく
(.[]をstubするのは適切?の問題は残ったままだけど)

:two: 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

:three: そもそもENVをstubしなければいけない状況を改善する

def hoge?
  ENV['HOGE'] == '1'
end
# または
config.hoge = ENV['HOGE']

というような感じにして#hoge?config.hogeをstubする
コードも読みやすくなるはずし、この対応ができるとベストだと思う

69
48
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
69
48