dogwood008の開発メモ!

最近のマイブームは機械学習, Ruby on Rails。中でも機械学習を使った金融商品の自動取引に興味があります。

【RSpec】letは遅延評価、後から上書きできる

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_usercurrent_user を返すように定義しているが、実はこの時点では未だ current_user は評価されていない。実際に it ブロックが呼ばれた際、初めて before が評価され、その時になって初めて before ブロック内から参照されている current_user が評価される。そのため、 context で条件を分けて書いたような記法が可能になる。

context 'when logged out' のブロックでは current_userUser.guest と評価されるので、 ApplicationController#current_userUser.guest が返る。同様に、 context 'when logged in' のブロックも、 FactoryBot.create(:user) が返る。

このように書くと、 context 内の記述量を減らすことができ、各 context 間での違いを比較しやすくできる。

なお、 let によく似た構文として let! がある。これは let とは異なり、定義したその時点で評価されるので、必要な場合を除いて、できるだけ let で宣言するのが望ましい。