こんにちは。アルバイトのハオです。今回はRailsのspecテストに役に立つfactory_bot(元factory_girl)について紹介したいと思います。FactoryBotで、テスト用のDBのレコードを便利に設定して作成することができますよ。

Railsアプリを用意

Railsアプリを新規作成し、モデル「User」と「Organization」を作成しましょう。

bundle exec rails generate model User name email organization_id

bundle exec rails generate model Organization name

bundle exec rake db:migrate を忘れないでくださいね。

UserはOrganizationに所属するようにしたいので、has_manybelongs_toを設定しましょう。

app/models/organization.rb
class Organization < ApplicationRecord
  has_many :users
end
app/models/user.rb
class User < ApplicationRecord
  belongs_to :organization

  def self_intro
    "私の名前は#{name}です"
  end

  def self.filter_users_with_email
    self.where.not(email: [nil, ""])
  end
end

RspecとFactoryBotの導入

まずGemfileにgemを追加:

group :test do
  gem 'rspec-rails'
  gem "factory_bot_rails"
end
bundle install忘れないでくださいね。

さらにrspecをインストールしていきましょう。

bundle exec rails generate rspec:install

するとフォルダーspecとファイルspec/rails_helper.rbspec/spec_helper.rbを作ってくれます。

FactoryBot

FactoryBotは、テスト用のオブジェクトを生成するための、オブジェクトの「工場」をつくるような存在です。

フォルダーspecの下に、フォルダーfactoriesを作成し、その下に「工場」のファイルを書いてみましょう。

まずはモデルOrganizationの「工場」です。書いた通り、コラムnameが指定した値に定義されています。

spec/factories/organization.rb
FactoryBot.define do
  factory :organization, class: Organization do
    name {"organization1"}
  end
end

そしてモデルUserです。

spec/factories/user.rb
FactoryBot.define do
  factory :user, class: User do
    sequence(:name) { |i| "test_#{i}_san"}

    trait :with_email do
      email {"test_trait@test.co.jp"}
    end

    association :organization

  end
end

ここで、sequence, trait, associationなどの見慣れないキーワードが出ていますね。それぞれを紹介していきます。

sequenceでコラムnameを設定することで、オブジェクトを生成するごとに番号が+1、ユニークなnameを設定できます

traitに入ったコード、指定される限りに行います。

associationはhas_many、belongs_toなどのactiverecord関連のあるモデルの間しか効きません。このオブジェクト(ここはUserのオブジェクト)が生成されると、associationが指定したモデル(ここはOrganization)もオブジェクト一つを生成してくれます。関連も付きます(ここはuserのorganization_idも自動に設定されます)。

テストファイル

では、テストファイルを書きましょう。

spec/model/model_test.rb

require 'rails_helper'

RSpec.describe "モデルテスト", type: :model do

  it "ユーザーを一つ作成(組織一つも自動に生成)" do
    FactoryBot.create(:user)
    user = User.first
    expect(user.organization).to be_present
  end

  it "一つの組織が複数のユーザーを持つ" do
    FactoryBot.create(:user)
    FactoryBot.create(:user, organization_id: 1)
    org = Organization.first
    expect(org.users.size).to eq 2
  end

  it "メールを持っているユーザーだけをフィルター" do
    user = FactoryBot.create(:user)
    FactoryBot.create(:user, :with_email)
    users = User.filter_users_with_email
    expect(users.size).to eq 1
  end

  it "ユーザーのメソッドself_intro" do
    FactoryBot.create(:user, name: "ルーター")
    user = User.first
    intro = user.self_intro
    expect(intro).to eq "私の名前はルーターです"
  end

end

一番上のit “ユーザーを一つ作成(組織一つも自動に生成)”から見ていきましょう。FactoryBot.create(:user)で、Userのオブジェクトを一つ、spec/factories/user.rbに書いた設定(ここは、nameがtest_1_sanに設定された)の通りに生成します。expect(user.organization).to be_presentで、生成したユーザーが所属する組織も生成されたかを確認します。

次のit “一つの組織が複数のユーザーを持つ”を見ていきましょう。FactoryBot.create(:user, organization_id: 1)で、生成したユーザーオブジェクトの、spec/factories/user.rbにorganization_idの設定をオーバーライドし、1に設定します。

次のit “メールを持っているユーザーだけをフィルター”にいきましょう。このitは、モデルUserのクラスメソッドfilter_users_with_emailの効果を検証します。FactoryBot.create(:user, :with_email)で、spec/factories/user.rbに書いてあるwith_emailというtraitを起こし、emailを持つユーザーひとつを作成します。filter_users_with_emailを実行し、取得するレコードはemailを持つユーザー一個のみのはずです。

最後のit “ユーザーのメソッドself_intro”は、モデルUserのメソッドself_introの効果を検証します。ここもFactoryBot.create(:user, name: "ルーター")でnameを指定していますね。

最後に

いかがですか?FactoryBotでテストを書くと、テスト用レコードの作成は楽になりますよ。皆様もどんどん使ってみてください!

参考資料

  • https://qiita.com/Ushinji/items/522ed01c9c14b680222c
  • https://qiita.com/metheglin/items/47116ccbdb26aa00e034
  • https://qiita.com/sabinuki/items/e64278ce775582f72634