FactoryBot内でDBに保存されているデータが使いたい

こんにちは!oda@エンジニア1年目です!

先日、FactoryBotでテストデータを作成する際にDBに保存されているデータを使いたかったのですが、方法がすぐにはわかりませんでした。

今回は、その方法について調べた内容を具体例とともにまとめました。

事前準備

まずはFoodモデルとOrderモデルを次のとおり定義します。

次にOrderのインスタンス作成時に使用するマスタデータとして、Foodモデルに以下のデータをあらかじめ準備しておきます。

id name price
1 弁当A 500

Orderpriceには、基本的にfood_idで指定したFoodpriceを使います。

それでもorder.food.priceのように呼び出さず、わざわざOrderpriceカラムを設けている理由は、弁当Aはpriceを個別に400円や350円に値引きして設定したいときがあるというイメージです。

テストの作成

今回のテストにはRSpecを使います。作成したテストは次のとおりです。

spec/model/order_spec.rb

require 'rails_helper'

RSpec.describe Order, type: :model do
  context 'create' do
    it '正しく注文が作成されていること' do
      food = Food.find(1)
      order = FactoryBot.create(:order, food_id: food.id)
      expect(order.food_id).to eq food.id
      expect(order.price).to eq food.price
    end
  end
end

失敗するケース

まずはテストが失敗するケースから確認していきます。

FactoryBotを次のとおり作成します。

spec/factories/orders.rb

FactoryBot.define do
  factory :order do
    food_id { 1 }
    price { 100 }
    quantity { 1 }
  end
end

テストを実行すると、結果は次のとおりです。

$ rspec spec/models/order_spec.rb
F

Failures:

  1) Order create 正しく注文が作成されていること
     Failure/Error: expect(order.price).to eq food.price
     
       expected: 500
            got: 100
     
       (compared using ==)
     # ./spec/models/order_spec.rb:9:in `block (3 levels) in <top (required)>'

Finished in 0.18945 seconds (files took 4.97 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/models/order_spec.rb:5 # Order create 正しく注文が作成されていること

orderの作成時に、設定しているのはfood_idのみなので、当然priceはFactoryBotで指定している100のままです。

spec/model/order_spec.rbでorder = FactoryBot.create(:order, food_id: food.id, price: food.price)とすることで上記を解決することはできます。

ただ、そのテストとしてpriceに意味がある値を設定する時以外はわざわざ書きたくありません。

このため、FactoryBot内でDBに保存されているデータを使ってpriceを設定したいと感じました。

FactoryBot内でDBのデータを呼び出す

調べているとFactoryBotでコールバックを使用する方法が見つかったので、こちらを試してみます。

参考:factory_bot/GETTING_STARTED.md

先ほどのFactoryBotを次のとおり修正します。

spec/factories/orders.rb

FactoryBot.define do
  factory :order do
    before(:create) do
      food = Food.find(1)
    end
    food_id { food.id }
    price { food.price }
    quantity { 1 }
  end
end

これでFactoryBot.createでテストデータが作成される前にbefore(:create)が実行され、DBから呼び出したデータがFactoryBot内で使えるようになるはずです。

テストを実行すると、期待どおりうまくいきました!

$ rspec spec/models/order_spec.rb
.

Finished in 0.25619 seconds (files took 5.71 seconds to load)
1 example, 0 failures

さいごに

業務中は、時間的な制約もありこの方法を調べて使うことができませんでした。

今回の記事を書く中で、この方法が使えそうだということの確認ができたので、次の機会で活かせることが楽しみです!