LoginSignup
25
18

More than 3 years have passed since last update.

Rspecの関連まわりで「バリデーションに失敗しました」が発生するとき

Last updated at Posted at 2019-05-13

問題

User has_many Cup(s)
Cup belongs_to User
という関連を作成したあと、以下のようなFactoryを作成。

users.rb

spec/factories/users.rb
FactoryBot.define do

  factory :user do
    name { "user" }
    email { "user@example.com" }
    password { "test123" }
    password_confirmation { "test123" }
  end

end

cups.rb

spec/factories/cups.rb
FactoryBot.define do

  factory :cup do
    user
    comment { "This is my cup" }
  end

end

cups.rbのfactory :cupの中に、userを記載している。

この状態で、テストに以下のような記載をすると
バリデーションに失敗しましたのエラーが発生してしまう。

describe "GET #index" do
  context "with user" do
    let(:user) { create(:user) }
    let(:cup) { create(:cup) }

    before do
      sign_in(user)
    end

    it "gets response 200" do
      expect(response.status).to eq 200
    end

    # 以下itから始まるテストが続く
    # ここでは省略する

  end
end
[1] pry(<RSpec::ExampleGroups::UserCupsResponse::POSTCreate::WithUser>)> cup
ActiveRecord::RecordInvalid: バリデーションに失敗しました: Emailはすでに存在します, Nameはすでに存在します
from /Users/reiforu/dev/cofapp/vendor/bundle/ruby/2.5.0/gems/activerecord-5.2.3/lib/active_record/validations.rb:80:in `raise_validation_error'

nameerrorはuser.rbのカラムで、models/user.rbにて
どちらもuniqueness: trueを付与している。

私の解決法

let(:cup) { create(:cup) }でバリデーションエラーが出たってことは、
userが既に作られているということ?

けど、cup.rbにはuserを記載して関連づけたし、このuserは存在しなければならない。
だから、let(:user)も記載しなくちゃいけないよな。。


あ、でもここでlet(:user)して既にuserができちゃってるのがダメなのかも。。

試しにlet(:user)消してみよう。

describe "GET #index" do
  context "with user" do
    let(:cup) { create(:cup, user: user) }
1] pry(<RSpec::ExampleGroups::UserCupsResponse::POSTCreate::WithUser>)> cup
+----+-------------------------+-------------------------+----------------+---------+
| id | created_at              | updated_at              | comment        | user_id |
+----+-------------------------+-------------------------+----------------+---------+
| 3  | 2019-05-12 05:37:28 UTC | 2019-05-12 05:37:28 UTC | This is my cup | 1       |
+----+-------------------------+-------------------------+----------------+---------+
1 row in set

登録できた!

てことは、cup作成時にuserというfactoryを【新たに】作成するってことか。

cup.rbuserを記載して関連付けを定義したから、
そのuserを別途let(:user)として呼び出さなきゃいけないと思ってた。。

けど

これだとbefore〜do内のsign_in(user)usernilになってログインできない。。

よし、じゃあ試しにlet(:user)を記載した上で、
let(:cup)userを付与してみよう。

describe "GET #index" do
  context "with user" do
    let(:user) { create(:user) }
    let(:cup) { create(:cup, user: user) }
[1] pry(<RSpec::ExampleGroups::UserCupsResponse::POSTCreate::WithUser>)> cup
+----+-------------------------+-------------------------+----------------+---------+
| id | created_at              | updated_at              | comment        | user_id |
+----+-------------------------+-------------------------+----------------+---------+
| 3  | 2019-05-12 05:21:12 UTC | 2019-05-12 05:21:12 UTC | This is my cup | 1       |
+----+-------------------------+-------------------------+----------------+---------+
1 row in set

登録できたー!そして、sign_in(user)もうまく実行できた。

まとめ

spec/factories/cups.rb
FactoryBot.define do

  factory :cup do
    user
    comment { "This is my cup" }
  end

end

factoryに上記userのような定義をした場合、

cupインスタンスの作成時に新たにuserを作成し、関連づける。
この際、let(:user)などを定義していて既に同じuserがいるとバリデーションエラーが発生する。

●しかし、sign_in(user)のように、引数にどうしてもuserが必要な際は、
テスト内でlet(:user)を作成し、そのusercupに関連付ける。

    # こんな感じ
    let(:user) { create(:user) }
    let(:cup) { create(:cup, user: user) }


おわり

25
18
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
25
18