RSpecのletを動的(どうてき)使用(しよう)する

RSpecで let動的(どうてき)定義(ていぎ)使用(しよう)するようにして shared_context使(つか)いまわしたかったのでメモ。

まずはletがどのように実装(じっそう)されているか確認(かくいん)する

letの定義(ていぎ)()てみると、

def let(name, &block)
  ︙
  # Apply the memoization. The method has been defined in an ancestor
  # module so we can use `super` here to get the value.
  if block.arity == 1
    define_method(name) { __memoized.fetch_or_store(name) { super(RSpec.current_example, &nil) } }
  else
    define_method(name) { __memoized.fetch_or_store(name) { super(&nil) } }
  end
end

上記(じょうき)のように define_method使(つか)われており、 let一見(いっけん)変数(へんすう)定義(ていぎ)しているようですが実際(じっさい)関数(かんすう)定義(ていぎ)していました。

結論(けつろん)

なので send使(つか)えばPHPでいう可変変数(かへんへんすう)のように()()すことができます。

shared_context 'foobar' do |label|
  let("#{label}_y") { rand(-10..10) }
  let("#{label}_x") { rand(-10..10) }
  let("#{label}_result") { Math.atan2(send("#{label}_y"), send("#{label}_x")) }
end

# 使用例は以下のような感じ
context 'hogefuga' do
  include_context 'foobar', 'hoge' # hoge_y, hoge_x, hoge_result が定義される
  include_context 'foobar', 'fuga' # fuga_y, fuga_x, fuga_result が定義される

  it { expect(hoge_result).to be_between(-Math::PI, Math::PI).inclusive }
  # it内でもsendでletを動的に呼べる
  it { expect(send('fuga_result')).to be_between(-Math::PI, Math::PI).inclusive }
end