Modelにインスタンスメソッド定義した時にちゃんとメソッドが通るか rails console で確認したりするのですが、クラスの親子関係が複雑になると rails console でインスタンス作るのが面倒になります。
そういう時はFactoryBotを使ってテスト環境にインスタンス作成してspec書いた方が楽です。
spec書いた時に気づいたtraitの使い方とこういう時にtransient使えるなっていうメモ
想定モデル(適当)
- ReservationとMenuは一対多の関係
- ホットペッパービューティーみたいに一つの予約に、カットとかシャンプーとかのたくさんのメニューをいれれるイメージ
Reservation.rb
class Reservation
has_many :menus, dependent: :destroy
validates :costomer_name, presence: true
validates :phone_number, presence: true
# menuの合計金額を得るメソッド
def total_price_from_menu
total_price = Money.new(0)
menus.each do |menu|
total_price += menu.price
end
total_price
end
end
Menu.rb
class Menu
belongs_to :reservation
validates :name, presence: true
validates :price_cents
# Moneyオブジェクト
monetize :price_cents
end
FactoryBot
- Factoryを定義する
- traitはfactoryの特定の状態に名前をつけて自由につけ外しができるようにする。下記ではreservationをcreateした時に子要素のmenuも同時に生成するようにtraitを定義した。
- transientは作成するデータと関係ないattributeを生成する。下記ではtransientのmenu_numの数だけ子要素のインスタンスを作成する。
spec/factories/reservations.rb
FactoryBot.define do
factory :reservation do
customer_name { "山田太郎" }
phone_number { "00000000000" }
trait :with_menus do
transient do
menu_num { 1 }
end
after(:create) do |reservation, evaluator|
evaluator.menu_num.times do
create(:menu, reservation: reservation)
end
end
end
end
spec/factories/menus.rb
FactoryBot.define do
factory :menus do
association :reservation, factory: :reservation
name { "カット" }
price { "1000" }
end
end
RSpec
- テストの実行
- with_menuで子要素が作られる
- menu_numで引数を指定することで任意の数のインスタンスを作れる
- インスタンスメソッドが期待どうりの挙動をするか確認する
spec/app/models/reservation_spec.rb
require 'rails_helper'
RSpec.describe Reservation, type: :model do
describe "#total_price_from_menus" do
let(:reservation) { create(:reservation, with_menu, menu_num: 2) }
it "期待通りの合計金額になる" do
expect(reservation.total_price_from_menu.format).to eq "200円"
end
end
end
参考: traitの切り替えやassoiationについては以下の記事が詳しい
FactoryBot(旧FactoryGirl)で関連データを同時に生成する方法いろいろ
FactoryGirlのtransientとtraitを活用する