Rails
RSpec

RSpec の letとlet!とbeforeの挙動と実行される順番

let と let! の違いは何となく理解していたが、
let! と before の実行順まで把握していなかったので、
この機会に備忘録的にそれぞれをまとめてみました。

let

let は 最初にメソッドが呼ばれた時 に評価されます。

なので例えば
User が複数の Article を持つ場合

◯◯_spec.rb
describe 'let' do
  let(:user) { create(:user) }
  let(:user_article) { create(:article, user_id: user.id) }

  specify 'User が Article を持っていること' do
    expect(user.articles.first).to eq user_article
  end
end

とやるとテストが通らない。。。はず。(面倒臭くて実際にはこのコード書いてない。すみません。)

この場合、

let(:user_article) { create(:article, user_id: user.id) }

が呼ばれるのは

expect(user.articles.first).to eq user_article

の中で "user_article" が呼ばれた時で

"user.articles.first" が処理される時にはまだ実行されていないので "user.articles.first" は空となりテストは通りません。

let!

let! は letが遅延評価であるのとは違い、各サンプルが実行される前に評価されます。
つまり各 it とか specify が実行される直前に評価されるので

先程のテストコードは

◯◯_spec.rb
describe 'let' do
  let!(:user) { create(:user) }
  let!(:user_article) { create(:article, user_id: user.id) }

  specify 'User が Article を持っていること' do
    expect(user.articles.first).to eq user_article
  end
end

このように let を let! に置き換えると specify のブロックが実行される前に

let!(:user)
let!(:user_article)

の両方が処理されるのでテストが通るようになります。

before、before(:each)

let! と同じ。
"before(:each)" と "before(:all)" の違いなどは今は書いていないので
別の記事あたりを参考にしていただければと

それぞれの実行される順番

let と let! は先程述べたタイミングで処理されるので
それぞれの定義の順番は関係ありません。

let! と before の実行タイミングは同時なので、
定義の順番通りに実行されます。

下記のコードの場合

◯◯_spec.rb
before { create(:user) }
let!(:user_article) { create(:article, user_id: user.id) }
before { create(:user_2) }

上から順に処理が行われていく(サンプルコード悪くてすみません。)

さいごに

まだまだわかったようで理解しきれていない部分もあると思うので、
またわかることあれば追記していきたいと思います。

また、不備等あれば教えていただければ幸いです。