子レコードがないと保存ができない親モデルの連動テストを作ろうとして四苦八苦した話。

#前回までのあらすじ
親モデルと子モデルの単体テストはできたのですが、親モデルが子モデルのレコードがなくても保存できてしまっていることに気付きました。
今回は子レコードが保存できない場合に親レコードで保存できないようにバリデーションを組もうとして四苦八苦したことを記事に残していこうと思います。

#下準備
[前回記事](https://qiita.com/0thefool/items/d38c109a0c2c76feb164)とか[前々回記事](https://qiita.com/0thefool/items/5e5935204c5a62f8bcce)とか[その前の記事](https://qiita.com/0thefool/items/5af0f17f0075e7e0e013)とかの下準備を参照ください。

#今回のテスト環境の準備
###バリデーションの追加
```validates :images,presence: true```を追記します。

```app/models/items.rb
（中略）
  validates :title,:images,presence: true #:imagesを追記
```

###インスタンスの作成
いくつかのパターンを想定してインスタンスを作成しました。
images.rbのインスタンスの内訳は以下です。
```:image```レコードの保存が期待できるインスタンス
```:without_image```imageがnullで保存に失敗することが期待されるインスタンス
```:with_text```imageにテキストファイルを保存しようとして保存に失敗することが期待されるインスタンス

```spec/factoreis/image.rb
FactoryBot.define do
  factory :image do
    image    { Rack::Test::UploadedFile.new(File.join(Rails.root, 'spec/fixtures/image.jpg'))}
    association :item

    factory :without_image ,class:Image do
      image    {""}
      association :item
    end

    factory :with_text ,class:Image do
      image    { Rack::Test::UploadedFile.new(File.join(Rails.root, 'spec/fixtures/image.txt'))}
      association :item
    end
  end
end
```

items.rbのインスタンスの内訳は以下です。
```:item``` 保存が期待できるインスタンス
```:item_without_name``` nameがnullで保存に失敗することが期待されるインスタンス
```:item_without_image``` :without_imageのインスタンスで子モデルのレコードの保存に失敗して、親レコードも保存に失敗することが期待されるインスタンス
```:item_with_text``` :with_textのインスタンスで子モデルのレコードの保存に失敗して、親レコードも保存に失敗することが期待されるインスタンス

```spec/factoreis/items.rb
FactoryBot.define do
  factory :item do
    name    {"test"}
    after(:build) do |item|
      item.images << build(:image,item: item)
    end

    factory  :item_without_name,class:Item do
      name  {""}
    end

    factory :item_without_image,class:Item do
      after(:build) do |item|
        item.images << build(:without_image,item: item)
      end
    end

    factory :item_with_text,class:Item do
      after(:build) do |item|
        item.images << build(:with_text,item: item)
      end
    end
  end
end
```

###テストコードを記述

```spec/models/image_spec.rb
require 'rails_helper'

RSpec.describe Image, type: :model do
  describe '#create' do
    let(:image) {build_stubbed(:image)}
    context 'can save' do
      it "is valid with a image,item_id" do
        expect(image).to be_valid
      end
    end
    context 'can not save' do
      it "is invalid without a image" do
        expect(build_stubbed(:without_image)).to be_invalid
      end

      it "is invalid with a image with filetype of not image file" do
        expect(build_stubbed(:with_text)).to be_invalid
      end

      it "is invalid without a item_id" do
        image.item_id = ""
        expect(image).to be_invalid
      end
    end
  end
end
```

```spec/models/item_spec.rb
require 'rails_helper'

RSpec.describe Item, type: :model do
  describe '#create' do
    let(:item) {build(:item)}
    context 'can save' do
      it "is valid with a name,images" do
        expect(item).to be_valid
      end
    end

    context 'can not save' do
      it "is invalid without a name" do
        expect(build(:item_without_name)).to be_invalid
      end
      it "is invalid without images" do
        expect(build(:item_without_image)).to be_invalid
      end
      it "is invalid with a file is type of text" do
        expect(build(:item_with_text)).to be_invalid
      end
    end
  end
end
```
準備ここまで。

#テストの実行結果
```Terminal.
bundle exec rspec spec/models/

```

```
Image
  #create
    can save
      is valid with a image,item_id
    can not save
      is invalid without a image
      is invalid with a image with filetype of not image file
      is invalid without a item_id

Item
  #create
    can save
      is valid with a name,images (FAILED - 1)
    can not save
      is invalid without a name
      is invalid without images
      is invalid with a file is type of text

Failures:

  1) Item#create can save is valid with a title
     Failure/Error: expect(item).to be_valid
       expected #<Item id: nil, title: "test", created_at: nil, updated_at: nil> to be valid, but got errors: Images item can't be blank
     # ./spec/models/item_spec.rb:8:in `block (4 levels) in <top (required)>'

Finished in 1.17 seconds (files took 5.74 seconds to load)
8 examples, 1 failure

Failed examples:

rspec ./spec/models/item_spec.rb:7 # Item#create can save is valid with a name,images
```

:itemの保存ができずに保存に失敗してしまいました。

#原因を探る
imageモデルの```validates :item_id,presence: true```が原因でitemの保存ができていませんでした。

```app/models/image.rb
class Image < ApplicationRecord
(中略)
  validates :image,presence: true  #:item_idを削除
end
```

ただ、外部キーの```item_id```が```null```でも保存できてしまい、imageモデルのテストが失敗する懸念があります。

#結果
```Terminal.
bundle exec rspec spec/models/
```

```
Image
  #create
    can save
      is valid with a image,item_id
    can not save
      is invalid without a image
      is invalid with a image with filetype of not image file
      is invalid without a item_id

Item
  #create
    can save
      is valid with a name
    can not save
      is invalid without a name
      is invalid without images
      is invalid with a file is type of text

Finished in 1.11 seconds (files took 5.81 seconds to load)
8 examples, 0 failures
```

杞憂でした。

#教訓
親モデルにバリデーションをかければ、子モデルの外部キーのバリデーションは必要ない（多分）

今回は以上です。