コード日進月歩

しんくうの技術的な小話、メモ、つれづれ、など

RSpecのbeforeでインスタンス変数を使いたくなったら、letを使うことを考えたほうがよさそう

letを使ったほうがいいはあるが、インスタンス変数を使うデメリットに関してはあんまりないので書いてみる。

環境

$ bundle exec rspec -v
RSpec 3.8
  - rspec-core 3.8.0
  - rspec-expectations 3.8.2
  - rspec-mocks 3.8.0
  - rspec-rails 3.8.1
  - rspec-support 3.8.0

今回想定するケース

対象となるモデル

# == Schema Information
#
# Table name: users
#
#  id         :bigint(8)        not null, primary key
#  name       :string(255)
#  created_at :datetime         not null
#  updated_at :datetime         not null
#

class User < ApplicationRecord
 
end

インスタンス変数を使う場合

  describe "#create" do
    before do
      @user = User.create(name: "Taro")
    end

    it "正しくcreateできる" do
      expect(@user.present?).to eq(true)
    end
  end

インスタンス変数を使わない場合

  describe "#create" do
    let(:user) {User.create(name: "Taro")}
    it "正しくcreateできる" do
      expect(user.present?).to eq(true)
    end
  end

インスタンス変数を見直したがほうがいい理由

letに変えると遅延評価になるので、利用されないシーンでは無駄に呼ばれることがない

let だと必要なタイミングで呼び出されるが、before でのインスタンス変数定義だと全ての場合に呼び出される。そのため context などで場合わけしたときに不要な場面でも作られることになる。

RSpecの場合、レコード生成やDBアクセスが実行時間に影響するケースが多いのでなるべくであれば生成する契機は少なくしたほうがいいので、そのような観点でもインスタンス変数は避けたほうがいい。

インスタンス変数だとnilとしてテストが実行される

インスタンス変数の場合は未定義の場合はnilになる。

例えば例に上げたインスタンス変数版のテストで、インスタンス変数名を間違えているとする

  describe "#create" do
    before do
      # @usersにタイポ
      @users = User.create(name: "Taro")
    end

    it "正しくcreateできる" do
      expect(@user.present?).to eq(true)
    end
  end

そうすると nil になってテストが実行される。

$ bundle exec rspec spec/models/user_spec.rb 
F

Failures:

  1) User#create 正しくcreateできる
     Failure/Error: expect(@user.present?).to eq(true)
     
       expected: true
            got: false
     
       (compared using ==)
     # ./spec/models/user_spec.rb:28:in `block (3 levels) in <top (required)>'

Finished in 0.01941 seconds (files took 1.25 seconds to load)
1 example, 1 failure

(例としては微妙だが)たとえば @user.nameがnilである というようなテストをぼっち演算子つかった場合は意図と反して通ってしまう

  describe "#create" do
    before do
      # user を users にタイポ
      @users = User.create(name: nil)
    end

    it "nameはnil" do
      expect(@user&.name).to eq(nil)
    end
  end

letを使うと user が定義されていないでテストが失敗するので気づける

  describe "#create" do
    let(:users) {User.create(name: nil)}
    it "nameはnil" do
      expect(user&.name).to eq(nil)
    end
  end
$ bundle exec rspec spec/models/user_spec.rb 
F

Failures:

  1) User#create nameはnil
     Failure/Error: expect(user&.name).to eq(nil)
     
     NameError:
       undefined local variable or method `user' for #<RSpec::ExampleGroups::User::Create:0x00007f8fbb2c4b68>
       Did you mean?  users
     # ./spec/models/user_spec.rb:18:in `block (3 levels) in <top (required)>'

参考リンク