はじめに
スクールでフリマアプリ開発の際、出品フォームのバリデーションテストを担当しました。
その時、出品画像のバリデーションテストで苦労したので、復習も兼ねて記事に残そうと思います。
今回のケース
- 商品名や商品説明を保存するテーブル(productsテーブル)と、出品画像を保存するテーブル(imagesテーブル)が分かれて存在している。
- アソシエーションは、has_many :images と、belongs_to :product の関係である。
- 1つのフォームから、複数のテーブルにデータを送信して保存する実装をしている。
- Productモデルに、validates :images , presence: true を記載しており、画像が無いと商品を出品できないようになっている。
問題なのは、画像が無いと出品ができないため、
フォーム上に画像がアップロードされている状態を、テストで再現しなくてはいけません。
このような場合、どうやってテストすればいいのでしょか?
それについて記述していきます。
テストの方法
まずはfactory_botを使って、imageの情報を含まないProductモデルのインスタンスを準備してみます。
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についてなので、それ以外の値については気にしなくて大丈夫です。
この状態で、とりあえず下記のテストを実行してみます。
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とは別に準備します。
FactoryBot.define do
factory :image do
image {File.open("#{Rails.root}/spec/fixtures/test_image.png")}
product
end
end
imageの準備ができたら、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
3行だけ追記しました。
after(:build) do |product|
product.images << build(:image, product: product)
end
after(:build) do 〜 end の部分で、productのインスタンスが作成された直後に、imageのインスタンスを作成して、product.imagesの中にimageインスタンスの情報を入れてあげています。
これでフォーム上に画像がアップロードされた状態を再現することができました!
もう一度テストを実行してみます。
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
今度は成功ですね!!
画像の枚数を変更したい場合
after(:build) do |product|
product.images << build_list(:image, 10, product: product) #buildをbuild_listへ変更
end
build を build_listメソッドにすることで、画像の枚数も操作することができます。
上記の場合は、build_listの第2引数に10を渡している為、画像が10枚アップロードされた状態を再現しています。
境界値などをテストする場合は、build_listを使うといいと思います。
所感
テストに関しては、検索しても情報が少なく、苦労しました。
この記事が少しでも誰かの役に立てば嬉しいです。
初投稿ですので、間違い等ございましたら、ご指摘いただけると幸いです。
ここまで読んでいただき、ありがとうございました!!
参考にさせていただいた記事
accepts_nested_attributes_forで作成するpresence:trueを付けた子モデルに対するモデルのバリデーションチェック~RSpec~
FactoryBotとCarrierWaveを使ってRSpecに画像ファイルアップロードのテストを通す