RSpecの世界では、 let
は遅延評価される。これは宣言した値が本当に必要とされる時まで評価を遅延するということを指す。具体的な例を持って説明する。
例えば下記のようなテストを行うとする。ここでは、あるAPIのエンドポイントにPOSTする際のステータスコードをテストしている。この中では context
でログイン時・非ログイン時の分岐がされている。
# ※実際にこのコードで動作を確認したわけではなく、あくまで解説用。 RSpec.describe User, type: :request do describe '/api/v1/users/me' do let(:path_to_endpoint) { '/path/to/endpoint' } let(:params) { { key: 'value' }.to_json } let(:headers) do { 'content-type': 'text/javascript' } end before do allow_any_instance_of(ApplicationController).to( receive(:current_user).and_return(current_user)) end subject do post(path_to_endpoint, headers: headers, params: params) end context 'when logged out' do let(:current_user) { User.guest } it { subject; expect(response).to have_http_status(401) } end context 'when logged in' do let(:current_user) { FactoryBot.create(:user) } it { subject; expect(response).to have_http_status(200) } end end end
この時、 before
ブロックの中で ApplicationController#current_user
が current_user
を返すように定義しているが、実はこの時点では未だ current_user
は評価されていない。実際に it
ブロックが呼ばれた際、初めて before
が評価され、その時になって初めて before
ブロック内から参照されている current_user
が評価される。そのため、 context
で条件を分けて書いたような記法が可能になる。
context 'when logged out'
のブロックでは current_user
は User.guest
と評価されるので、 ApplicationController#current_user
も User.guest
が返る。同様に、 context 'when logged in'
のブロックも、 FactoryBot.create(:user)
が返る。
このように書くと、 context
内の記述量を減らすことができ、各 context
間での違いを比較しやすくできる。
なお、 let
によく似た構文として let!
がある。これは let
とは異なり、定義したその時点で評価されるので、必要な場合を除いて、できるだけ let
で宣言するのが望ましい。