LoginSignup
6
5

More than 5 years have passed since last update.

Rspecのbeforeとletの挙動を検証

Last updated at Posted at 2017-07-31

rspecの挙動が分かりづらかったので簡単にまとめてみました。

Gemfile.lock

    rspec (3.6.0)
      rspec-core (~> 3.6.0)
      rspec-expectations (~> 3.6.0)
      rspec-mocks (~> 3.6.0)
    rspec-core (3.6.0)
      rspec-support (~> 3.6.0)
    rspec-expectations (3.6.0)
      diff-lcs (>= 1.2.0, < 2.0)
      rspec-support (~> 3.6.0)
    rspec-mocks (3.6.0)
      diff-lcs (>= 1.2.0, < 2.0)
      rspec-support (~> 3.6.0)
    rspec-rails (3.6.0)
      actionpack (>= 3.0)
      activesupport (>= 3.0)
      railties (>= 3.0)
      rspec-core (~> 3.6.0)
      rspec-expectations (~> 3.6.0)
      rspec-mocks (~> 3.6.0)
      rspec-support (~> 3.6.0)
    rspec-support (3.6.0)

検証コード

create(:model)factorygirlで何かしらのApplicationRecordのオブジェクトを作る想定

require 'rails_helper'

RSpec.describe do
  let(:model) do
    m = create(:model)
    p 'outer let:' + m.id.to_s
    m
  end
  before do
    m = create(:model)
    p 'outer before:' + m.id.to_s
  end
  before(:each) do
    m = create(:model)
    p 'outer before(each):' + m.id.to_s
  end
  before(:all) do
    m = create(:model)
    p 'outer before(all):' + m.id.to_s
  end

  context do
    let(:model) do
      m = create(:model)
      p 'inner let:' + m.id.to_s
      m
    end
    before do
      m = create(:model)
      p 'inner before:' + m.id.to_s
    end
    before(:each) do
      m = create(:model)
      p 'inner before(each):' + m.id.to_s
    end
    before(:all) do
      m = create(:model)
      p 'inner before(all):' + m.id.to_s
    end
    subject do
      m = model
      p 'subject1:' + m.id.to_s
      m
    end

    example { expect(subject.id).to eq(model.id) }
    it do
      m = model
      p 'it:' + m.id.to_s
      expect(subject.id).to eq(m.id)
    end
  end

  context do
    subject do
      m = model
      p 'subject2:' + m.id.to_s
      m
    end

    example { expect(subject.id).to eq(model.id) }
  end
end

とした場合、アウトプットは下記の通りになる

"outer before(all):1"
"inner before(all):2"
"outer before:3"
"outer before(each):4"
"inner before:5"
"inner before(each):6"
"inner let:7"
"subject1:7"
"outer before:8"
"outer before(each):9"
"inner before:10"
"inner before(each):11"
"inner let:12"
"it:12"
"subject1:12"
"outer before:13"
"outer before(each):14"
"outer let:15"
"subject2:15"

まとめ

  • テストの実行単位は it or exampleであり、基本的に実行単位ごとに変数は初期化される(以下、このテスト実行単位をテストプロセスと表記します)
  • beforeメソッドはブロックのスコープ内で有効
  • before(:all)はブロック内で一度実行される(ブロック内にテストプロセスが複数あっても実行は一度のみ)
  • beforebefore(:each)は同じ。ブロック内のテストプロセスごとに実行される
  • subjectlet(:subject)の略。テスト対象を表す変数。expect(subject).tois_expected.toと略せる。
  • letについて
    • 遅延評価変数。ブロック内でコールされたタイミングでletブロック内の処理を行い結果をletの引き数の変数に格納
    • 同じブロック内で複数回コールされる場合、初回にletブロック内の処理を行いその結果のオブジェクトを、2回目以降は初回の実行時に作成されたオブジェクトを返却する
    • ブロックのスコープ内とスコープ外の2箇所で同じ名前のlet変数が定義されていた場合、ブロック内のletだけが評価される

おまけ(letとlet!)

require 'rails_helper'

RSpec.describe do
  let(:model) do
    m = create(:model)
    p 'outer let:' + m.id.to_s
    m
  end

  context do
    let!(:dummy_model) do
      m = create(:model)
      p 'dummy let!:' + m.id.to_s
      m
    end
    subject do
      m = model
      p 'subject:' + m.id.to_s
      m
    end

    example { expect(subject.id).to eq(model.id) }
    it do
      m = model
      p 'it:' + m.id.to_s
      expect(subject.id).to eq(m.id)
    end
  end
end
"dummy let!:1"
"outer let:2"
"subject:2"
"dummy let!:3"
"outer let:4"
"it:4"
"subject:4"
  • let!は変数がコールされなくてもテストプロセスのはじめに実行される(beforeの前か後かはコードの記述順で決まる)
6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5