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