requestspecを使ってコントローラーテストを行っていたのですが、let!の使い方に関して不明点があったため、記録として残しておきます。
letを始めRSpecに関する基礎的な文法についてはこちらの記事を参考にさせていただきました。
使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」
##開発環境
rails (6.0.3.3)
rspec-rails (4.0.1)
疑問内容
####なぜletではなくlet!なのか。
destroy以外では:laundry
に対してletで通ったのですがdestroyアクションの時だけテストが通らずlet!にするとテストが通ります。
挙動としては以下を想定しています。
- 管理者(admin: true) → 削除を実行
- 管理者以外(admin: false) → 削除は行われずトップページにリダイレクトする
コードは以下のとおりです。(該当箇所のみ抜粋しています。)
describe '#destroy' do
let(:admin_user) { FactoryBot.create(:user, admin: true) }
let(:user) { FactoryBot.create(:user, admin: false) }
#この部分がなぜlet!になるのか
let!(:laundry) { FactoryBot.create(:laundry) }
context '管理者の場合' do
it '店舗情報を削除できること' do
sign_in admin_user
expect {
delete laundry_path(laundry)
}.to change { Laundry.count }.by(-1)
end
end
context '認可されていないユーザーの場合' do
it '店舗情報を削除できないこと' do
sign_in user
expect {
delete laundry_path(laundry)
}.to_not change { Laundry.count }
end
end
end
##結論
###deleteアクションを実行する前にデータをセットしておくため。
処理実行の流れとしては以下のとおりです。
1.Laundry.count
が実行される(処理実行前のデータの数を確認)
2.expect { ... }が実行される(削除の実行)
3.Laundry.countが実行される(削除実行後のデータの数を確認)
先ほどのコードと処理実行の流れを照らし合わせながらみていきます。
- let(遅延評価)にした場合
let(:admin_user) { FactoryBot.create(:user, admin: true) }
let(:user) { FactoryBot.create(:user, admin: false) }
let(:laundry) { FactoryBot.create(:laundry) }
context '管理者の場合' do
it '店舗情報を削除できること' do
sign_in admin_user
expect {
delete laundry_path(laundry)
}.to change { Laundry.count }.by(-1)
end
end
1.Laundry.count
が実行される(処理実行前のデータの数を確認)
→ FactoryBot.create(:laundry)
は実行されていないためLaundry.count
は0
2.expect { ... }が実行される(削除の実行)
→ delete laundry_path(laundry)
でデータは作成されるが削除されるためこちらもLaundry.count
は0
3.Laundry.countが実行される(削除実行後のデータの数を確認)
→ 予想は1つデータが減っているはず(to change { Laundry.count }.by(-1))
だが、上記1.2からデータ数に変化はないためエラーが発生する。
エラー内容
expected `Laundry.count` to have changed by -1, but was changed by 0
削除できないことを確認したい場合も同様の流れでエラーが発生します。
it '店舗情報を削除できないこと' do
sign_in user
expect {
delete laundry_path(laundry)
}.to_not change { Laundry.count }
end
1.データは作成されていないためLaundry.count
は0
2.expext{}で削除を行うが管理者ではないため削除は実行されない(Laundry.countは1)
3.to_not change { Laundry.count }
でデータ数は変わっていないことを確認したいが0から1に変更しているためエラーが発生する。
expected `Laundry.count` not to have changed, but did change from 0 to 1
以上からletで遅延評価にすることによりエラーが発生してしまいます。
これをlet!にして事前評価(各itが実行される直前にデータを作成)にすることでLaundry.count
が1からスタートし、テストがパスします。
##まとめ
createアクションの場合はデータが増えることを確認したいため前もってデータを用意する必要はないですが、destroyアクションの場合はデータが減ることを確認する必要があるためlet!を使う必要があります。
以上です。ありがとうございました!