関連する子モデルの情報を含んだフォームのバリデーションテスト(Rspec)

## はじめに
スクールでフリマアプリ開発の際、出品フォームのバリデーションテストを担当しました。
その時、出品画像のバリデーションテストで苦労したので、復習も兼ねて記事に残そうと思います。

## 今回のケース
<img width="600" alt="0a9433558f4c9427fbc2f041c4edba3c.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/580274/9b62b800-7510-4849-228b-56a3206ff0e5.png">

- 商品名や商品説明を保存するテーブル(productsテーブル)と、出品画像を保存するテーブル(imagesテーブル)が分かれて存在している。
- アソシエーションは、has_many :images　と、belongs_to :product　の関係である。
- １つのフォームから、複数のテーブルにデータを送信して保存する実装をしている。
- Productモデルに、validates :images , presence: true　を記載しており、画像が無いと商品を出品できないようになっている。

問題なのは、画像が無いと出品ができないため、
フォーム上に画像がアップロードされている状態を、テストで再現しなくてはいけません。
このような場合、どうやってテストすればいいのでしょか？

それについて記述していきます。

## テストの方法
まずはfactory_botを使って、imageの情報を含まないProductモデルのインスタンスを準備してみます。

```ruby:spec/factories/products.rb
FactoryBot.define do
  factory :product do  #画像なしバージョン
    id                {"1"}
    name              {"商品名"}
    detail            {"詳細"}
    category_id       {"686"}
    which_postage     {"1"}
    prefecture        {"1"}
    shipping_date     {"1"}
    price             {"1000"}
    size
    condition
    sending_method
    user
  end
end
```

今回注目して欲しいのはimageについてなので、それ以外の値については気にしなくて大丈夫です。

この状態で、とりあえず下記のテストを実行してみます。

```ruby:spec/models/product_spec.rb
require 'rails_helper'
describe Product do
  describe '#create' do
    it "全ての必須項目が入力されている場合出品できる" do
      product = FactoryBot.build(:product)
      expect(product).to be_valid
    end
  end
end
```

```:ターミナル
Product
  #create
    全ての必須項目が入力されている場合出品できる (FAILED - 1)

Failures:

  1) Product#create 全ての必須項目が入力されている場合出品できる
     Failure/Error: expect(product).to be_valid
       expected #<Product id: 1, name: "商品名", detail: "詳細", shipping_date: 1, price: 1000, which_postage: 1, delivery_status: "出品中", prefecture: 1, created_at: nil, updated_at: nil, brand_id: nil, user_id: 1, size_id: 1, condition_id: 1, sending_method_id: 1, buyer_id: nil, category_id: 686> to be valid, but got errors: 商品画像を入力してください
     # ./spec/models/product_spec.rb:154:in `block (3 levels) in <top (required)>'

Finished in 0.63978 seconds (files took 5.52 seconds to load)
1 example, 1 failure
```

当然ですが、「商品画像を入力してください」と言われ、テストは失敗します。
product.imagesの中身が空っぽな訳です。
なんとかしてproduct.imagesへ画像の情報を入れてあげなければいけません。

その為に、imageのfactoryをproductとは別に準備します。

```ruby:spec/factories/images.rb
FactoryBot.define do

  factory :image do
    image   {File.open("#{Rails.root}/spec/fixtures/test_image.png")}
    product
  end

end
```

imageの準備ができたら、spec/factories/products.rb を以下のように編集します。

```ruby:spec/factories/products.rb
FactoryBot.define do
  factory :product do  #画像ありバージョン
    id                {"1"}
    name              {"商品名"}
    detail            {"詳細"}
    category_id       {"686"}
    which_postage     {"1"}
    prefecture        {"1"}
    shipping_date     {"1"}
    price             {"1000"}
    size
    condition
    sending_method
    user
    after(:build) do |product|                           #追記
      product.images << build(:image, product: product)  #追記
    end                                                  #追記
  end
end
```

３行だけ追記しました。

```ruby
after(:build) do |product|
  product.images << build(:image, product: product)
end
```

after(:build) do 〜 end　の部分で、productのインスタンスが作成された直後に、imageのインスタンスを作成して、product.imagesの中にimageインスタンスの情報を入れてあげています。

これでフォーム上に画像がアップロードされた状態を再現することができました！
もう一度テストを実行してみます。

```ruby:spec/models/product_spec.rb
require 'rails_helper'
describe Product do
  describe '#create' do
    it "全ての必須項目が入力されている場合出品できる" do
      product = FactoryBot.build(:product)
      expect(product).to be_valid
    end
  end
end
```

```:ターミナル
Product
  #create
    全ての必須項目が入力されている場合出品できる

Finished in 0.54654 seconds (files took 5.16 seconds to load)
1 example, 0 failures
```

今度は成功ですね！！

### 画像の枚数を変更したい場合
```ruby
after(:build) do |product|
  product.images << build_list(:image, 10, product: product) #buildをbuild_listへ変更
end
```

build を build_listメソッドにすることで、画像の枚数も操作することができます。
上記の場合は、build_listの第２引数に１０を渡している為、画像が１０枚アップロードされた状態を再現しています。
境界値などをテストする場合は、build_listを使うといいと思います。

## 所感
テストに関しては、検索しても情報が少なく、苦労しました。
この記事が少しでも誰かの役に立てば嬉しいです。
初投稿ですので、間違い等ございましたら、ご指摘いただけると幸いです。
ここまで読んでいただき、ありがとうございました！！

## 参考にさせていただいた記事
[accepts_nested_attributes_forで作成するpresence:trueを付けた子モデルに対するモデルのバリデーションチェック~RSpec~](https://umakki.com/archives/309)
[FactoryBotとCarrierWaveを使ってRSpecに画像ファイルアップロードのテストを通す](https://qiita.com/Katya/items/803c6ac67cca73e9087e)