Ma2Matsu0121
@Ma2Matsu0121

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

(Ruby on Rails) Rspecでのモデル単体テストコードでscopeを用いたuniquenessのテストをしたい

解決したいこと

Ruby on Railsでポートフォリオ作成中の初学者です。
現在モデルの単体テストコードを行うべく、記述を書いているのですが、一箇所scopeを用いた一意性のテストコードについて書き方が分かりませんでした。
ターミナルでテストコードを走らせた際、"日付はすでに存在しています"というエラーメッセージを出力してほしいものですが、それ以外のエラーメッセージしか表示されません。
テストコード以前に、そもそもデータ保存の段階でバリデーションが効いているかを確認しましたが、そちらでは問題なく"日付はすでに存在しています"のエラーメッセージが出てくれました。

発生している問題・エラー

ターミナル

〜省略〜
 1) Schedule データ保存 データが保存できない 同一ユーザーで同じ日に重複したデータは保存できない
     Failure/Error: expect(schedule.errors.full_messages).to include("")
       expected ["Userを入力してください", "お酒の種類を入力してください", "お酒の種類を選択してください", "度数を入力してください", "度数は数値で入力してください", "度数は不正な値です", "お酒の量を入力してください", "お酒の量は数値で入力してください", "お酒の量は不正な値です"] to include ""
     # ./spec/models/schedule_spec.rb:25:in `block (4 levels) in <top (required)>'

Finished in 0.19726 seconds (files took 1.38 seconds to load)
14 examples, 1 failure
〜省略〜

"expected"の中に"日付はすでに存在しています"というメッセージを出力させたいです。

該当するソースコード

テストコード
app>factories>schedules.rb

FactoryBot.define do
  factory :schedule do
    alcohol_id         {2}
    percentage         {5}
    amount             {500}
    start_time         {"2022-7-20"}
    association :user
  end
end

該当箇所は"start_time"です。

app>models>schedule_spec.rb

〜省略〜
context 'データが保存できない' do
        it '同一ユーザーで同じ日に重複したデータは保存できない' do
          @schedule.save
          schedule = Schedule.create
          schedule.start_time = "2022-7-20"
          schedule.valid?
          expect(schedule.errors.full_messages).to include("")
〜省略〜

このコードが間違っています。
内容としては、"models"の@scheduleがデータベースに保存されて、その後同一ユーザーが新しくscheduleをデータベースに保存するときに”start_time"を@scheduleの日付とお同じ時間にした場合バリデーションが発動するといった内容にしたかったつもりです。

モデル
app>models>schedule.rb

class Schedule < ApplicationRecord
  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to_active_hash :alcohol
  belongs_to :user
 
  with_options presence: true do
    validates :start_time, uniqueness: { scope: :user }
    with_options numericality: { other_than:0,message: 'を選択してください'} do
      validates :alcohol_id
    end
    validates :percentage, numericality:{ greater_than: 0, less_than_or_equal_to: 100 },format: { with: /\A[0-9]+\z/ }
    validates :amount, numericality: { greater_than: 0, less_than_or_equal_to: 99_999 },format: { with: /\A[0-9]+\z/ }
  end

end

validates :start_time, uniqueness: { scope: :user }の部分が該当のバリデーションです。
別ユーザー間では"start_time"が重複しても問題ありませんが、同一ユーザーが同じ日付にデータを保存した場合にバリデーションが発動するようにしています。

自分で試したこと

schedule_spec.rbの記述をいじくりまわしましたが、解決できませんでした。原因、解決方法がわかる方がいらっしゃいましたらご教授いただけますと幸いです。

0

3Answer

考え方としては

  1. ユニークな状態を満たす同じ状態のインスタンスが2つ以上待ち構えている
  2. 片方が先にデータベースに登録を完了させた
  3. 他が後からデータベースに更新しようとしてもエラーになる、ということを確認
describe :validations do
  describe :start_time do
    let(:user){ create :user }
    subject { build :schedule, user: user }

    context "uniqueness" do
      it do
        expect{
          # 同じ条件で先にこっちが登録してしまう
          create :schedule, user: subject.user, start_time: subject.start_time
        }.to change{
          # とすると他方はエラーになるということを確認
          subject.valid?
        }.to(false)
      end
    end
  end
end
0Like

RSpec.configure do |config|
  config.include FactoryBot::Syntax::Methods
end

というのを使っていたので、FactoryBot.createというのを省略するものです。
テストを書く際はこういうなんらかのファクトリーパターンを使って対象オブジェクトを作ったほうが楽です。

使っていないなら

# create :user
User.create ...

# build :schedule
Schedule.new ...

と置き換えてみてください。単純にそれぞれのオブジェクトを準備しているだけです。

0Like

Your answer might help someone💌