はじめに
Rspecを追加していて、1つのテスト項目で複数のモデルの個数が増えていること・変化しないことを検証する必要がありました
その時に便利なものを見つけたのでメモで残します
背景
userを消したときに紐づくSNSの中間テーブルは削除、紐づくSNSアカウントは削除しないという処理がありました
具体的な例
以下のようなuserテーブルを削除したときのモデルの個数を検証するテストでした
has_many :youtube_certs, dependent: :destroy
has_many :youtube_users, through: :youtube_certs
has_many :instagram_certs, dependent: :destroy
has_many :instagram_users, through: :instagram_certs
has_many :twitter_certs, dependent: :destroy
has_many :twitter_users, through: :twitter_certs
簡単にいうと
userを削除・・・・
XXX(userに紐づく各SNS)_certsテーブルは削除
XXX(userに紐づく各SNS)_usersテーブルは削除しない
1番最初の書き方
1行ずつ記載していたが、無駄な処理が多い・・・
context "インフルエンサーを削除できる" do
it "各SNSの_certsテーブルが削除されること" do
expect { delete user_path(user) }.to change(YoutubeCert, :count).by(-1)
expect { delete user_path(user) }.to change(InstagramCert, :count).by(-1)
expect { delete user_path(user) }.to change(TwitterCert, :count).by(-1)
end
it "各SNSの_usersテーブルは削除されないこと" do
expect { delete user_path(user) }.to change(YoutubeUser, :count).by(0)
expect { delete user_path(user) }.to change(InstagramUser, :count).by(0)
expect { delete user_path(user) }.to change(TwitterUser, :count).by(0)
end
end
マッチャ合成式を使用した書き方
重複が減って削除する処理の回数も2回に減った
context "インフルエンサーを削除できる" do
it "各SNSの_certsテーブルが削除されること" do
expect do
delete user_path(user)
end.to change(YoutubeCert, :count).by(-1).and change(InstagramCert, :count).by(-1).and change(TwitterCert, :count).by(-1)
end
it "各SNSの_usersテーブルは削除されないこと" do
expect do
delete user_path(user)
end.to change(YoutubeUser, :count).by(0).and change(InstagramUser, :count).by(0).and change(TwitterUser, :count).by(0)
end
複数モデルの個数が減るパターンのテストは完成!!
しかし・・・個数が変わらないことのテストでrubocopの指摘
Prefer negated matchers with compound expectations over change.by(0).
→change.by(0)
を使用せずにnot_to change
の使用を提案された
not_to changeを使用した書き方
context "インフルエンサーを削除できる" do
~省略~
it "各SNSの_usersテーブルは削除されないこと" do
expect do
delete user_path(user)
end.to_not change(YoutubeUser, :count).and to_not change(InstagramUser, :count).and to_not change(TwitterUser, :count)
end
上記の書き方だとエラーが発生
NoMethodError:
undefined method `to_not' for #......>
not_to change ... and to_not change ...
のような書き方はできないみたいでした
以下がとても参考になりました!
URL:Rspecで複数のモデルのカウントが増加しないことを検証する
Define negated matcherを使用するとうまくいくみたい
マッチャの否定形を自分で作ることができるみたいです
# 第一引数:自分が定義したい否定系のマッチャ
# 第二引数:否定系にしたい既存のマッチャ
# 定義する場所:マッチャを使いたいテストがあるspecファイルの先頭などに記載する
RSpec::Matchers.define_negated_matcher :not_change, :change
Define negated matcherを使用した書き方
context "インフルエンサーを削除できる" do
~省略~
it "各SNSの_usersテーブルは削除されないこと" do
# 今回追加した
RSpec::Matchers.define_negated_matcher :not_change, :change
expect do
delete user_path(user)
end.to not_change(YoutubeUser, :count).and not_change(InstagramUser, :count).and not_change(TwitterUser, :count)
end
無事テストが通りました
まとめ
Define negated matcherを使用すると複数モデルの個数の増減両方一気に検証こともできました
→not_changeとchangeをandで繋いで検証できる!!!
context "インフルエンサーを削除できる" do
~省略~
it "各SNSの_usersテーブルは削除されないこと" do
RSpec::Matchers.define_negated_matcher :not_change, :change
expect do
delete user_path(user)
end.to not_change(YoutubeUser, :count).and change(InstagramCert, :count).by(-1)
end
参考
Rspecで複数のモデルのカウントが増加しないことを検証する
RSpecのDefine negated matcherが地味に便利