1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Rspecで複数モデルの個数の増減を1つのテストで検証するには

Last updated at Posted at 2023-10-02

はじめに

Rspecを追加していて、1つのテスト項目で複数のモデルの個数が増えていること・変化しないことを検証する必要がありました

その時に便利なものを見つけたのでメモで残します

背景

userを消したときに紐づくSNSの中間テーブルは削除、紐づくSNSアカウントは削除しないという処理がありました

具体的な例

以下のようなuserテーブルを削除したときのモデルの個数を検証するテストでした

models/user.rb
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行ずつ記載していたが、無駄な処理が多い・・・

spec/user_spec.rb
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回に減った

spec/user_spec.rb
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を使用した書き方

spec/user_spec.rb
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を使用した書き方

spec/user_spec.rb
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で繋いで検証できる!!!

spec/user_spec.rb
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が地味に便利

1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?