👗

【Rspec】【shoulda-matchers】ユニーク制約を付加しているバリデーションでのエラーを解消

2024/08/28に公開

はじめに

現在アプリ開発がひと段落したため、Rspecを用いてテストコードを書いております。
表題にある通り、ユニーク制約を付加しているバリデーションでテストコードを書いております。
その中で、Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher::ExistingRecordInvalid:というエラーが発生し、エラー解消まで時間を要したので、備忘録のためにも記事に残したいと思います。
参考になりましたら幸いです。

現状

  • gem 'rspec-rails'、gem 'factory_bot_rails'、gem 'shoulda-matchers',は導入済み
  • gem 'shoulda-matchers'を導入した経緯等は、こちらの記事に投稿しております。

現状のコードと、エラー内容

今回は、commentfavoriteモデルのテストコードを書いております。

  • app/models/commentfavorite.rb
class Commentfavorite < ApplicationRecord
  belongs_to :user
  belongs_to :comment

  validates :comment_id, uniqueness: { scope: :user_id }
end
  • spec/models/commentfavorite_spec.rb
require 'rails_helper'

RSpec.describe Commentfavorite, type: :model do
  it { is_expected.to validate_uniqueness_of(:comment_id).scoped_to(:user_id) }
end

shoulda-matchersの、ActiveRecord matchersであるvalidate_uniqueness_ofでテストコードを記載し、テストを通したところ下記のエラーが出ました。

Image from Gyazo

上記エラーの翻訳内容が下記です。

validate_uniqueness_of は、新しいレコードを既存のレコードと照らし合わせて検証します。既存のレコードが存在しない場合は、提供されたレコードを使って新しいレコードを作成します。
その際、次のエラーが発生しました:
このエラーは、commentfavorites テーブルの user_id カラムに null 値が挿入されているため発生しています。このカラムには null を許可しない制約があります。
この問題を解決するには、マッチャーに対して必要な属性が正しい値で埋められたレコードを提供することが最善です。

何やらレコード関連でエラーが生じている...。ということは理解できましたが、テストコードに慣れていないためどうすればいいかわからず、エラー文をそのまま検索にかけ、こちらの記事を発見しました。

  • 事前にレコードを作成するような記述を追加する必要があること
  • レコードが存在しない場合は作成してテスト実行するため、その他のカラムのDBの制約によってはDBエラーが発生してしまう

どのようにテストコードを改修したのか

上記の記事を参考に、subject { FactoryBot.build(:commentfavorite) }を追記した形でテストを通しました。
しかし、こちら記載してもエラーが出てしまい、テストが通りませんでした...。
(格闘に夢中になっており、エラー内容は失念)

結果、FactoryBot 内でそのアソシエーションを正しく設定できていなかったため、テストに失敗していました。
下記はテストコードが通ったコード内容です。

  • spec/models/commentfavorite_spec.rb
require 'rails_helper'

RSpec.describe Commentfavorite, type: :model do
  subject { FactoryBot.build(:commentfavorite) }

  it { is_expected.to validate_uniqueness_of(:comment_id).scoped_to(:user_id) }
end
  • spec/factories/commentfavorites.rb
FactoryBot.define do
  factory :commentfavorite do
    association :user
    association :comment
  end
end
  • spec/factories/users.rb
FactoryBot.define do
  factory :user do
    name { "hogehoge" }
    sequence(:email) { |n| "user#{n}@example.com" }
    password { "hogehoge" }
    password_confirmation { "hogehoge" }
  end
end
  • spec/factories/articles.rb
FactoryBot.define do
  factory :article do
    title { "hogehoge" }
    body { "hogehoge" }
    association :user
  end
end
  • spec/factories/comments.rb
FactoryBot.define do
  factory :comment do
      body { "hogehoge" }
      association :user
      association :article
    end
end

今回のアプリの構成として、

  • ユーザーとコメント: 1対多
  • ユーザーと記事: 1対多
  • ユーザーとcommentfavorite: 1対多
    というアソシエーションを結んでいます。

当初FactoryBotでは其々のテストデータを生成する役割のみ果たしていると思っていましたが、
今回のテストコードを通しFactoryBot内でもアソシエーションを結ぶ必要があることを学びました。

まだテストコードが不慣れで、Rspecを使用したテスト全般でFactoryBot内でもアソシエーションを結ぶ必要があるのか、またはgem 'shoulda-matchers'を導入しているからなのかは調べきれていません。
まだテストコードは書き終わっていないので、調べながら進めていきます。
最後までご覧いただきまして、誠にありがとうございました。

Discussion