はじめに
こんにちは。アメリカ在住で独学エンジニアを目指している Taira です。
RSpec でテストを書くときによく出てくるletとlet!。
どちらもテストで使う変数を定義するためのヘルパーですが、動き方が少し違います。
この記事では、それぞれの特徴と使い分け方を、コード例を交えて解説します。
1. letとは?
letは**遅延評価(lazy evaluation)**される変数を定義します。
つまり、初めて呼び出されたときに実行されるという動きです。
RSpec.describe User do
let(:user) { User.create(name: "Taro") }
it "最初に呼び出したタイミングで実行される" do
# この時点ではUserはまだ作られていない
expect(User.count).to eq(0)
user # ← 初めて呼び出したタイミングでUserが作成される
expect(User.count).to eq(1)
end
end
✅ ポイント
- 実際に使われなければ、定義部分は実行されない
- 無駄なデータ作成を避けられる(テスト高速化につながる)
2025/08/12 追記
RSpec に関する記事をあさってたら、let の遅延評価をうまく使っている記事を発見したので追記しておきます
- 遅延評価を利用することで、場合分けを表現できる。
詳しくは以下の記事を見ていただくとわかりやすいと思います
2. let!とは?
let!は**即時評価(eager evaluation)**されます。
テストが始まる前に必ず実行されるため、呼び出さなくても変数が作られます。
RSpec.describe User do
let!(:user) { User.create(name: "Taro") }
it "テスト開始前にすでに作成されている" do
# let! は呼び出さなくても事前に実行される
expect(User.count).to eq(1)
end
end
✅ ポイント
- 呼び出さなくても必ず実行される
- 事前にデータを用意しておきたいときに便利
3. 違いをまとめると
| 特徴 | let | let! |
|---|---|---|
| 実行タイミング | 初回呼び出し時 | 各テストの実行前 |
| 呼び出さない場合 | 実行されない | 必ず実行される |
| 主な用途 | 必要なときだけデータを用意 | 前提条件として必ずデータを用意 |
4. 使い分けの目安
letを使うべきとき
- 不要なデータ作成を避けたいとき
- テストの中で呼ばれるかわからないが、とりあえず変数を用意しておきたいとき
let(:article) { create(:article) } # 呼ばれない限りDBにINSERTされない
let!を使うべきとき
- テスト開始前に必ずデータが必要なとき
-
beforeブロックでセットアップする代わりに使うとスッキリする場合
let!(:article) { create(:article) } # 各itの前に必ず作られる
5. 実務でよくあるハマりポイント
❌「あれ、データがない…」
let(:user) { create(:user) }
it "ユーザー数を確認する" do
expect(User.count).to eq(1) # ← 0になる(userを呼び出してない)
end
➡ 解決策:必ず事前に必要ならlet!にするか、userを呼び出す。
6. まとめ
-
let→ 初回呼び出し時に実行(遅延評価) -
let!→ テスト開始前に実行(即時評価) - パフォーマンスを意識して
letを使い、**必須データはlet!**で作成する
💡 基本はlet、本当に必要なときだけlet!にするとテストが速く、意図しないデータ作成も防げます。