LoginSignup
0

More than 3 years have passed since last update.

letを使用することで、通るはずのテストが通らないFalse Alarmを生じる可能性がある

Last updated at Posted at 2020-07-28

先日RSpecのテストコードにレビューを頂いたので、

なるべく多くを学びたいと思い、その内容を咀嚼する過程での気付きを書きたいと思います

元記事
RSpecのEmail一意性テストで"Email has already been taken"問題と回避策 - Qiita

  • なるべく小さいscope?(contextの中など)を見ただけでテストの内容が把握できるような書き方のほうが、読み手に理解しやすい、想定外の挙動を防ぐことができる。

  • letを使って書くことはこれを実現するために有効

  • letの遅延評価によって、テストによっては必要のないインスタンス変数を毎回beforeで定義するような冗長性を回避できる

が、この遅延評価を理解せずに、なんとなく変数代入の感覚で使用していたために以下のようなエラーに遭遇しました

letを使用することで、通るはずのテストが通らないfalse Alarmを生じる可能性がある

letlet!の使い分けが重要になりそうなケース

  • テストを実施する時点と、前提となる条件の間に時間的なずれがある場合
  • すでに完了しているものと、現在の比較が必要な場合
  • モデルのテストの場合なら、一意性のテスト

遅延評価されるletが、実際にテストの失敗を招いたケース

レビューの内容を参考に、
属性の一意性についてのテストを書いてみた。

User modelの属性usernameの一意性についてのテストを以下のうように書くと

    describe "username" do
      ...
      context "usernameが重複している場合" do
        let(:existing_user) { FactoryBot.create(:user, username: "alice") }
        subject { FactoryBot.build(:user, username: "alice" ) }
        it { is_expected.to be_invalid }
      end
      ...
    end

モデルでusername属性についてのvalidationが記述されているにもかかわらず

class User < ApplicationRecord
  validates :username, presence: true, uniqueness: true, length: { maximum: 12 }
  ...
end

validationがinvalidにならない

1) User#create username usernameが重複している場合 is expected to be invalid
     Failure/Error: it { is_expected.to be_invalid }
       expected `#<User id: nil, email: "test_user_5@example.com", created_at: nil, updated_at: nil, username: "alice">.invalid?` to return true, got false

テストの記述を変更する
(実際にレビューコードではこの書き方を提示してくれていました)
let >> let!

    describe "username" do
      ...
      context "usernameが重複している場合" do
        let!(:existing_user) { FactoryBot.create(:user, username: "alice") } #let!に変更
        subject { FactoryBot.build(:user, username: "alice" ) }
        it { is_expected.to be_invalid }
      end
      ...
    end

このようにすると、想定通りテストがパスするようになる。

letlet!の違い

  • letは、itやexampleが実行されるまで評価されない
  • let!は即座に実行される

it { is_expected.to be_invalid }の時点で、existing_userは存在完了していないといけない
英文でいうところletは現在完了的な振る舞いをして、let!は過去完了の状態をつくってくれると考えると個人的にはしっくりきた。

参考

RSpecのletを使うのはどんなときか?(翻訳) - Qiita

使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」 - Qiita

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
0