FactoryBotのtrait、transient、evaluatorについて

概要

Railsのテストでテストデータを作成する際に便利なgemである、FactoryBotにあるtrait、transient、evaluatorの機能について備忘録として書きます。

trait

通常のテストデータの作成とは別に、モデルの属性値の集合を定義できる。共通の属性値を持ったテストデータの作成時、重複を避けることができる。
一例として、以下のようなUserのファクトリを作成します。

FactoryBot.define do
  factory :user do
    sequence(:name) { |n| "admin-#{n}" }
    password { 'password' }
    password_confirmation { 'password' }
    role { :admin }

    trait :writer do
      sequence(:name) { |n| "writer-#{n}" }
      role { :writer }
    end
  end
end

通常の書き方は以下の通りで、nameがadmin-1、roleがadminのデータを作成している。

let(:user) { create(:user) }

pry(~)> user
 => #<User:0x00007fd7e6a9f0e0
 id: 1,
 name: "admin-1",
 password: "password",
 password_confirmation: "password",
 role: "admin",
 created_at: Tue, 25 May 2021 03:14:48 JST +09:00,
 updated_at: Tue, 25 May 2021 03:14:48 JST +09:00>

次にtraitを活用したuserは、nameがwriter-1、roleがwriterのデータを作成している。

let(:writer) { create(:user, :writer) }

pry(~)> writer
 => #<User:0x00007fd7f23b4eb8
 id: 2,
 name: "writer-1",
 password: "password",
 password_confirmation: "password",
 role: "writer",
 created_at: Tue, 25 May 2021 03:15:04 JST +09:00,
 updated_at: Tue, 25 May 2021 03:15:04 JST +09:00>

transient

テストデータの元となるモデルに定義されてない任意の属性を定義することができる。
一例として、以下のようなUserのファクトリを作成します。

FactoryBot.define do
  factory :user do
    transient do
      state_name { 'user' }
    end

    name { "tanaka(#{state_name})" }
    password { 'password' }
    password_confirmation { 'password' }

    trait :admin do
      state_name { 'admin' }
    end

    trait :writer do
      state_name { 'writer' }
    end
  end
end

traitのadminを使用した場合、nameがtanaka(admin)のデータを作成している。

let(:admin) { create(:user, :admin) }

pry(~)> admin.name
=> "tanaka(admin)"

traitのwriterを使用した場合、nameがtanaka(writer)のデータを作成している。

let(:writer) { create(:user, :writer) }

pry(~)> writer.name
=> "tanaka(writer)"

evaluator

FactoryBotのcallbackとして渡されている引数。その中身は、FactoryBotで作成されたインスタンスも、transientで定義した属性もハッシュの形で返してくれるオブジェクト。transientで定義した属性を取り出したいときに必要となる引数。
一例として、Articleを作成後、一対多のアソシエーションを結んだAuthorモデルのデータを作成するファクトリを作成します。

FactoryBot.define do
  factory :article do
    sequence(:title) { |n| "title-#{n}" }
    state { :published }
  end

  trait :with_author do
    transient do
      sequence(:author_name) { |n| "test_author_name_#{n}" }
    end

    after(:build) do |article, evaluator|
      article.author = build(:author, name: evaluator.author_name)
    end
  end

with_authorのtraitを使用したデータを作成後、authorメソッドを使用することでAuthorモデルのデータを取得することができる。

let(:article_with_author) { create(:article, :with_author) }

pry(~)> article_with_author.author
=> #<Author:0x00007f9a263a0670
 id: nil,
 name: "test_author_name_1",
 created_at: nil,
 updated_at: nil>