1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Factorybotのtrait, transient, evaluator

Posted at

背景

  • Factorybotを利用してテストデータを作成しているプロジェクトにtraittransientを使い分けているファイルを発見した。
  • traitは知っていたがtransient, evaluatorを知らなかったので残しておく

結論

  • traitは、状態の切り替え
  • transientは、テスト用の一時変数
  • evaluatorは、transientの値を取得するためのオブジェクト

traitとは?

同じfactoryに対する状態の違いを名前付きでまとめたものです。

例えば、下記のfactoryが定義されているとします。

# spec/factories/users.rb
FactoryBot.define do
  factory :user do
    sequence(:name) { |n| "user#{n}" }
    sequence(:email) { |n| "user#{n}@example.com" }
    admin { false } # ここで管理者をbooleanで判定する
  end
end

このUserモデルのテストデータでは、adminというbooleanのカラムを保持しているとします。

ここがtrueであれば管理者であり、falseであれば管理者ではありません。

しかし、この記述のままだとcreate(:user)と呼び出すとadminフラグはfalseとして生成されることになります。

逆にadmin { true }と記述すると、管理者データであるユーザーデータのみを生成することになります。

そこでtraitを利用します。

# spec/factories/users.rb
FactoryBot.define do
  factory :user do
    sequence(:name) { |n| "user#{n}" }
    sequence(:email) { |n| "user#{n}@example.com" }
    admin { false }
    
    # こんな感じでtrueを定義
    trait :admin do
      admin { true }
    end
  end
end

そしてcreate(:user, :admin)と呼び出してあげると、管理者であるユーザーデータを生成することができ、create(:user)と呼び出してあげると、管理者ではないユーザーデータを生成することができます。

これがtraitを利用して状態の違いを名前付きで管理するということです。

transientとは?

factoryを生成するときに使う一時的な変数です。DBには保存されず、factory内部のロジックで使うためだけに存在します。

例えば、ユーザーに紐づく投稿(Post)を任意の件数作成したい場合を考えます。

# spec/factories/users.rb
FactoryBot.define do
  factory :user do
    sequence(:name) { |n| "user#{n}" }
    sequence(:email) { |n| "user#{n}@example.com" }

    transient do
      posts_count { 0 } # デフォルトは0件
    end

    after(:create) do |user, evaluator| # evaluatorは下記で説明しています
      create_list(:post, evaluator.posts_count, user: user)
    end
  end
end

このように定義すると、以下のように呼び出せます。

# 投稿なしのユーザー
create(:user)

# 投稿5件を持つユーザー
create(:user, posts_count: 5)

# 投稿10件を持つユーザー
create(:user, posts_count: 10)

posts_countはUserモデルのカラムではなく、factory内でのみ使われる一時変数です。

evaluatorとは?

after(:create)などのコールバック内で、transientで定義した値にアクセスするためのオブジェクトです。

after(:create) do |user, evaluator|
  # evaluator.posts_count で transient の値を取得できる
  create_list(:post, evaluator.posts_count, user: user)
end

呼び出し時に渡したposts_count: 10という値は、evaluator.posts_countで取得できます。

create(:user, posts_count: 10)
# → evaluator.posts_count は 10 になる

transienttraitの組み合わせ

transienttraitは組み合わせて使うこともできます。

# spec/factories/users.rb
FactoryBot.define do
  factory :user do
    sequence(:name) { |n| "user#{n}" }
    sequence(:email) { |n| "user#{n}@example.com" }
    admin { false }

    transient do
      posts_count { 0 }
    end

    trait :admin do
      admin { true }
    end

    trait :with_posts do
      transient do
        posts_count { 3 } # traitでデフォルト値を上書き
      end

      after(:create) do |user, evaluator|
        create_list(:post, evaluator.posts_count, user: user)
      end
    end
  end
end
# 投稿3件を持つ管理者ユーザー
create(:user, :admin, :with_posts)

# 投稿10件を持つ管理者ユーザー
create(:user, :admin, :with_posts, posts_count: 10)

感想

  • specの見通しを考えるとあまり乱用は避けた方がいいのかな?と思いつつ、かなり便利に作れそうだなぁと思った。

参考

公式はGETTING_STARTED.mdよりも↓を読むことを推奨しているらしい…

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?