環境
Ruby 2.5.0
RSpec 3.7.1
Rails 5.2.0
RSpecのlet/let!をうまく使うと、Beforeブロック内にインスタンス変数をあれこれ書いてぐちゃぐちゃなコードになってしまう展開を防げる。
1 2 3 4 5 6 7 8 9 10 11 12 |
describe 'インスタンス変数' do before do @book = create(:book) # factory bot @mock = Mock.new @book.id end it '本を入荷リストに追加する' do post 'path/to/stocklist/add', params: @mock.get_book_params, headers: @mock.get_headers expect(response.status).to eq 204 expect(StockList.last.book_id).to eq @book.id end end |
この例だけだと分かりにくいが、インスタンス変数をRspecで乱発すると後々だんだんと可読性が下がり変数の汚染も広がるので、letに置き換えてみる。
1 2 3 4 5 6 7 8 9 10 |
describe 'let化' do let(:book) { create(:book) } # factory bot let(:mock) { Mock.new book_id } it '本を入荷リストに追加する' do post 'path/to/stocklist/add', params: mock.get_book_params, headers: mock.get_headers expect(response.status).to eq 204 expect(StockList.last.book_id).to eq book.id end end |
ここで本題。
letに置き換えた後のコードについて、
1 |
let(:book) { create(:book) } # factory bot |
これはfactory botを利用したcreateだが、
1 |
post 'path/to/stocklist/add', params: mock.get_book_params, headers: mock.get_headers |
このpost先の処理が「create(:book)されていること」を前提に書かれていた場合、エラーになる。
なぜかというと、let(/let!)ブロック内の評価タイミングは「自身が初回に呼び出されたタイミング」だから。
つまり変な話、
1 2 3 4 5 6 7 8 9 10 11 |
describe 'let化' do let(:book) { create(:book) } # factory bot let(:mock) { Mock.new book_id } before do book end it '本を入荷リストに追加する' do post 'path/to/stocklist/add', params: mock.get_book_params, headers: mock.get_headers expect(response.status).to eq 204 expect(StockList.last.book_id).to eq book.id end end |
これだとitの前に必ずbookがコールされるのでこのテストは通るようになる。
letに置き換えるのはいいけど、実行されるタイミングは知っておこう、という話でした。