Rspecでテストコード を書く③ 〜2つのテーブルを同時に保存編〜
TECH::CAMP卒業まで残すところあと2週間となりました。
入学当初はできなかった事が、継続する事で本当にいろいろできるようになったと思っています。
できるようになったことは、できるだけアウトプット!
ということで、本日はテストコード の記事3つ目を書きたいと思います。
- 今回の記事の内容
- 参考記事
- 作業の前に(前回の復習)
- DB design
- itemモデルのバリデーション
- カテゴリーのFactoryBot、rspecの書き方
- imagesテーブルの処理の仕方
- テストコード
- まとめ
今回の記事の内容
テストコード を描けるようになった後でも結構詰まってしまった、2つのテーブルを同時保存するときのテストです。
メルカリのクローンサイトを作っているのですが、商品の出品時に、下記の2テーブルにデータを同時に保存しなければなりません。
- itemsテーブル(カテゴリーも保存しなければならない)
- imagesテーブル(itemsテーブル保存時に同時に保存しなければならない)
カテゴリーはgem 'ancestry'を使用しており、
親、子、孫カテゴリーを作成しているのですが、
itemsテーブルには必ず孫カテゴリーを保存するような設計にしているので、
そこも実現したい。
参考記事
下記記事を参考にさせていただきました!!!【Rails、RSpec】Ancestryを用いている時のFactoryBotの作り方 - Qiita
世界一わかりやすい!!複数画像投稿時のRSpecテストの書き方 | 目指せ、スーパーエンジニア
作業の前に(前回の復習)
- gemのインストール
group :test do gem 'rspec-rails' gem 'factory_bot_rails' gem 'faker', "~> 2.8" gem 'rails-controller-testing' #コントローラーのテストをする場合 end
- specディレクトリの作成(コマンド)
$ rails g spec:install
- .rspec ファイルへの追記
--format documentation
- rails.helper.rb ファイルへの追記
RSpec.configure do |config| config.include FactoryBot::Syntax::Methods #ここを追記 #省略 end
DB design
DB設計は下記の通り。アソシエーションも記載しています。
- itemsテーブル
Column | Type | Options |
------ | ---- | ------- |
name | string | null: false |
price | integer | null :false |
content | text | null :false |
category_id | references | null: false, foreign_key: true |
condition | integer | null: false |
brand | string | |
size | string | |
preparation_day | integer | null: false |
postage_type | integer | null: false |
postage_payer | integer | null: false |
seller_id | references | null: false, foreign_key: true |
buyer_id | references | foreign_key: true |
prefecture_id(active_hash) | integer | null: false |
- Association
- belongs_to :user
- belongs_to :category
- has_many :images
- imagesテーブル
Column | Type | Options |
------ | ---- | ------- |
item_id | references | null: false, foreign_key: true |
image_url | string | null: false |
- Association
- belongs_to :item
- categoriesテーブル
Column | Type | Options |
------ | ---- | ------- |
name | string | null: false |
ancestry | string |
- Association
- has_many :items
itemモデルのバリデーション
validates_associated :images validates :name, presence: true, length: { maximum: 40 } validates :content, presence: true, length: { maximum: 1000 } validates :category_id, presence: true validates :condition, presence: true validates :postage_payer, presence: true validates :postage_type, presence: true validates :prefecture_id, presence: true validates :preparation_day, presence: true validates :price, presence: true validates :seller_id, presence: true validate :images_number //カスタムバリデーション。画像は1個以上10個以下存在しなければならない。 def images_number errors.add(:images, "を1つ以上指定してください") if images.size < 1 errors.add(:images, "は10個までです") if images.size > 10 end
カテゴリーのFactoryBot、rspecの書き方
カテゴリーは、一旦親カテゴリーをFactoryBotで作ってやります。(ancestryカラムがnilなのが親カテゴリ)
FactoryBot.define do factory :category do name {"category"} ancestry {nil} end end ||<< 次にitemsテーブルのFactoryBotのなかで、ancestryで使えるメソッドを使用して 孫カテゴリを作り、保存するという動作を書きます。 >|ruby| FactoryBot.define do factory :item do name {"商品"} content {"good"} brand {"NIKE"} condition {1} postage_payer {1} postage_type {1} prefecture_id {1} preparation_day {1} price {1} buyer_id {} //出品時は空なので空にしています after(:build) do |item| parent_category = create(:category) child_category = parent_category.children.create(name: "child") grand_child_category = child_category.children.create(name: "grandchild") item.category_id = grand_child_category.id end end end
ここで初めて使ったのが、after(:build) という書き方。
rspecでitemモデルをbuildした後に加える動作を指示できるようです。
親カテゴリモデルを作成
↓
子カテゴリモデルを作成
↓
孫カテゴリモデルを作成
↓
孫カテゴリのidをitemsテーブルのcategory_idカラムに保存
という流れを記載しています。
imagesテーブルの処理の仕方
imagesテーブルのFactoryBotファイルは作りません。imagesテーブルの場合は、ダミーの画像をspec/fixturesディレクトリに保存しておき、
Rack::Test::UploadedFileモジュールで呼び出してやります。
その上で、カテゴリーと同様、itemモデルをを(:build)するときに、imageモデルも(:build)し、
item.imagesという配列に入れてやるという処理をします。
FactoryBot.define do factory :item do name {"商品"} content {"good"} brand {"NIKE"} condition {1} postage_payer {1} postage_type {1} prefecture_id {1} preparation_day {1} price {1} buyer_id {} //出品時は空なので空にしています after(:build) do |item| parent_category = create(:category) child_category = parent_category.children.create(name: "hello") grand_child_category = child_category.children.create(name: "world") item.category_id = grand_child_category.id item.images << FactoryBot.build(:image, item_id: item.id) end end factory :image do image_url { Rack::Test::UploadedFile.new(File.join(Rails.root, "spec/fixtures/sample.png"), 'image/png') } end end
これで準備完了!!
テストコード
カテゴリーと画像関連以外は省略しています。require 'rails_helper' describe Item do describe "#create" do let(:user) { create(:user) } let(:item) {build(:item, seller_id: user.id)} //seller_idが空やでとエラーが出たので常に足すようにしています。 it "category_idがないと出品できないこと" do item.category_id = nil item.valid? expect(item.errors[:category_id]).to include("を入力してください") end it "imageがないと出品できないこと" do item.images = [] //item.imagesの配列が空であるということにしています。 item.valid? expect(item.errors[:images]).to include("を1つ以上指定してください") end it "imageが10を超えると出品できないこと" do 10.times {|num| item.images << build(:image, item_id: item.id) //itemsモデルのFactoryBotで行った配列への画像挿入を、追加で10回行っています。 } item.valid? expect(item.errors[:images]).to include("は10個までです") end end
これで無事にテストが通りました・・・!!
まとめ
テストをする事で、つけ忘れているバリデーション に気づく、なんていう順番が逆じゃないかと言われそうなこともありました。
ですが、つけ忘れたバリデーション に気づくのも、きっとテストコード をかく役割なんじゃないかと思っています!
実際、最初にテストコード を書いて、それが通るようなコードを書くという方の記事を見つけました。
このコードをメンターさんに見てもらった時に、
ここまで書ける人は受講生の中でもなかなかいませんよ〜と言っていただき、
苦労が報われた気がして泣きそうになりました( ; ; )
最初わからない事でも、諦めずに頑張ればできるようになる!
これが本当にプログラミングの魅力・楽しみであると思います。
さらに世の中に役立つ物を作れれば、最高ですね!!!
卒業間近ですが、これからも楽しむことを忘れずに学習を続けていきたいと思います。