【Rspec】【shoulda-matchers】ユニーク制約を付加しているバリデーションでのエラーを解消
はじめに
現在アプリ開発がひと段落したため、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
でテストコードを記載し、テストを通したところ下記のエラーが出ました。
上記エラーの翻訳内容が下記です。
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