Hisakeyのブログ

エンジニアが色々呟くブログです。

RSpecのlet, let!, before の違いと使いどころ

はじめに

RSpec でテストを書く際に、データの準備方法として let, let!, before があります。

これらの使い分けを明確に理解していないと、意図しない動作をしてしまうことがあります。それぞれの使い方を記事にしてみました!


1. letlet! の違い

RSpec では、テストデータを定義するために letlet! がよく使われますが、両者には以下の違いがあります。

メソッド 実行タイミング 特徴
let 遅延評価(最初に参照されたとき) let(:user) { User.create(...) } のように定義しても、テストで user を参照しない限り実行されない
let! 即時評価 let!(:user) { User.create(...) } のように定義すると、テストの前に実行される

let の遅延評価

RSpec.describe User, type: :model do
  let(:user) { User.create(name: "Taro") }

  it "does not create a user if not accessed" do
    expect(User.count).to eq(0) # userを参照しないので作成されない
  end

  it "creates a user when accessed" do
    user # ここで user が参照され、作成される
    expect(User.count).to eq(1)
  end
end

userlet によって定義されていますが、明示的に user を参照しない限り User.create(...) は実行されません。

let! の即時評価

RSpec.describe User, type: :model do
  let!(:user) { User.create(name: "Taro") }

  it "creates a user before the test runs" do
    expect(User.count).to eq(1) # user を参照しなくても作成される
  end
end

let! を使うと、テストの前に User.create(...) が実行されるため、User.count は 1 になります。


2. beforelet! の違い

before もテストの前に処理を実行できますが、使い方に違いがあります。

before を使う場合

RSpec.describe User, type: :model do
  before do
    User.create(name: "Taro")
  end

  it "creates a user before the test runs" do
    expect(User.count).to eq(1)
  end
end

before は、指定したブロックの中で直接データを作成できます。

let! との比較

RSpec.describe User, type: :model do
  let!(:user) { User.create(name: "Taro") }

  it "creates a user before the test runs" do
    expect(User.count).to eq(1)
  end
end

どちらも同じように動作しますが、before では変数を定義しないため、テスト内で user を明示的に使用しない場合は let! の方が適しています。

どちらを使うべきか

  • before

    • 変数を定義せず、処理だけを実行したい場合
    • scopeなどのテストで、対象外のデータを作成するときに使用
  • let!

    • 変数を定義しつつ、前もってデータを作成したい場合
    • scopeなどのテストで、対象のデータを作成し、明示的に作成したい場合に使用

4. まとめ

  • let は遅延評価されるため、不要なデータ作成を防げるが、意図しない未作成の可能性がある
  • let! は即時評価されるため、常に事前にデータを用意できるが、不要なデータが作成されることもある
  • before は変数を定義せず、処理だけを実行するのに適している

以下のgithubで、詳しく説明されており、とてもわかり易かったのでおすすめです!

RSpec スタイルガイド github.com