8
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Factory bot】Rspecでのテストコード作成をもっと楽に!

Posted at

はじめに

こんにちは、エンジニア3年目の嶋田です。
この記事を開いていただきありがとうございます!

私がエンジニアとして働き始めてから、テストの重要性を痛感しています。
特にRspecを使用したテストは、コードの品質を保つために欠かせません。
そして、テストデータの生成にはFactory Botが非常に役立ちます。

今回は、Rspecで使用するFactory Botの基本的な使い方や便利な機能についてまとめてみました。
この記事が皆さんのテストコードの質を向上させる一助となれば幸いです。

より詳しく知りたい方は、以下のページを参照して下さい。
(※この記事の内容の大半は、以下のページに書かれている内容です)
Factory Bot Getting Started

目次

Factory Botとは

Factory BotはRspec標準で使える"fixture"に代わり、
テストデータの準備をサポートしてくれるライブラリです。

"factory_bot_rails"はRails向けの拡張版のFactorybot

主にRspecと組み合わせて使用され、テストデータを柔軟かつ効率的に作成することができます。

主な特徴

  • 簡潔なシンタックスでテストデータを定義
  • 依存関係を考慮したデータ生成
  • traitやコールバックを使った柔軟なデータ生成

Factoryの定義

まずは、Factoryの基本的な定義方法について説明します。
以下の例では、UserモデルのFactoryを定義します。

# spec/factories/users.rb
FactoryBot.define do
  factory :user do
    name { "John Doe" }
    email { "john.doe@example.com" }
    password { "password" }
  end
end
  • FactoryBot.define doブロックの中に、factory :user doと記述することでUserモデルのFactoryを定義します。
  • 各属性はブロック内で指定し、具体的な値を設定します。
    • この場合、nameemailpasswordがそれぞれ設定されています。

基本的なファクトリー定義方法

必要最低限の属性を持ったファクトリーを定義する

インスタンス化するのに必要最低限の属性を持ったファクトリーを各クラスで一つ定義します。
具体的には、オブジェクト作成時にバリデーションを通すために必要で、且つデフォルト値を持っていない属性を定義します。

# spec/factories/users.rb
FactoryBot.define do
  factory :user do
    sequence(:email) { |n| "test#{n}@example.com" } # シーケンスを使う
    first_name { "John" }
    last_name { "Doe" }
    date_of_birth { 18.years.ago } # 値を動的に生成する
    organization # Organizationへのアソシエーション
  end
end

ハッシュを扱う場合

JSONカラムなどを扱う場合は、以下のように{{ key: value }}とする必要があります。

# spec/factories/programs.rb
FactoryBot.define do
  factory :program do
    configuration { { auto_resolve: false, auto_define: true } }
  end
end

Factoryの使い方

次に、定義したFactoryを使用してデータを生成する方法を紹介します。
以下の例は、Rspecのテスト内でFactory Botを使用する方法です。

# spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  it "is valid with valid attributes" do
    user = FactoryBot.build(:user)
    expect(user).to be_valid
  end

  it "has a unique email" do
    user1 = FactoryBot.create(:user, email: "unique@example.com")
    user2 = FactoryBot.build(:user, email: "unique@example.com")
    expect(user2).not_to be_valid
  end
end
  • FactoryBot.build(:user)で、データベースに保存されていないUserオブジェクトを生成します。
  • FactoryBot.create(:user)で、データベースに保存されたUserオブジェクトを生成します。
    • 必要に応じて属性をオーバーライドすることもできます。

buildとcreateの使い分け

  • build:メモリ上にだけインスタンスを生成します。データベースへの影響はありません。
  • create:インスタンスを生成し、データベースに保存します。これにより、データベースの状態を変更するテストが可能になります。
# インスタンスを生成して返す。インスタンスはまだDBに保存されていない。
user = build(:user)

# DBに保存されたインスタンスを返す。
user = create(:user)

build_listとcreate_list

build_listcreate_listを使うと複数のインスタンスを生成できます。

users = build_list(:user, 2)
users = create_list(:user, 2)

属性を指定する

属性を指定することで、特定の条件に合ったインスタンスを生成できます。

# factoryとして定義されていなくても、テーブルに存在するカラムを指定可能
user = create(:user, first_name: "Dave", role: "admin")

traitの使用

traitを使用すると、Factoryに特定の状態や属性を追加することができます。以下に、traitを使用した例を示します。

# spec/factories/users.rb
FactoryBot.define do
  factory :user do
    name { "John Doe" }
    email { "john.doe@example.com" }
    password { "password" }

    trait :admin do
      admin { true }
    end

    trait :age_20 do
      age { 20 }
    end

    trait :age_19 do
      age { 19 }
    end

    trait :like_vegetables do
      like { "野菜" }
    end

    trait :like_fish do
      like { "魚" }
    end
  end
end
  • trait :admin doブロック内で、admin属性をtrueに設定します。
  • agelikeの属性もtraitとして定義することで、組み合わせの自由度が高くなります。

traitのメリット

traitの大きなメリットは、組み合わせの自由度の高さです。必要なテストデータを小さく分けて定義し、必要に応じて組み合わせることで、様々なテストシナリオに対応できます。

例えば、以下のように組み合わせて使用します。

it { expect(build(:user, :age_20, :like_vegetables).method_X).to eq("パターン1") }
it { expect(build(:user, :age_20, :like_fish).method_X).to eq("パターン2") }
it { expect(build(:user, :age_19, :like_vegetables).method_X).to eq("パターン3") }
it { expect(build(:user, :age_19, :like_fish).method_X).to eq("パターン4") }

このようにすることで、テストデータの意図が明確になり、必要なデータを簡単に組み合わせることができます。

具体例

例えば、20歳が閾値のテストがある場合、以下のようにtraitを利用してテストデータを作成します。

factory :user do
  name { "taro" }

  trait :age_20 do
    age { 20 }
  end

  trait :age_19 do
    age { 19 }
  end
end

この方法で、テストの軸が増えても対応しやすくなります。例えば、20歳以上と未満、好きな食べ物が野菜かそれ以外かの組み合わせで4パターンのテストデータを作成する場合も簡単です。

it { expect(build(:user, :age_20, :like_vegetables).method_X).to eq("パターン1") }
it { expect(build(:user, :age_20, :like_fish).method_X).to eq("パターン2") }
it { expect(build(:user, :age_19, :like_vegetables).method_X).to eq("パターン3") }
it { expect(build(:user, :age_19, :like_fish).method_X).to eq("パターン4") }

すっきりと意図が明確なテストデータが用意できるようになります。テスト作成者が自由に必要なデータを組み合わせることができます。

もちろん例のような簡単なケースなら、以下のように事前定義不要の可能性もあります。

build(:user, age: 20, like: "野菜")

しかし、汎用性がありそう、再利用される、初期化が手間、などの場合には、traitでテストデータを用意することも有益です。

コールバックの使用

コールバックを使用すると、オブジェクトの生成前後に特定の処理を実行できます。以下に、コールバックを使用した例を示します。

# spec/factories/users.rb
FactoryBot.define do
  factory :user do
    name { "John Doe" }
    email { "john.doe@example.com" }
    password { "password" }

    after(:create) do |user|
      create_list(:post, 3, user: user)
    end
  end
end

callbackとtransientを組み合わせる例

callbackをtransientと組み合わせて使うことで、transientが指定された時に関連付けられたデータを生成します。

# spec/factories/users.rb
FactoryBot.define do
  factory :user do
    # その他の属性

    transient do
      posts_count { 0 }
    end

    after(:create) do |user, evaluator|
      create_list(:post, evaluator.posts_count, user: user) if evaluator.posts_count.positive?
    end
  end
end

callbackとtraitを組み合わせる例

callbackをtraitと組み合わせて使うことで、traitが指定された時に関連付けられたデータを生成します。

# spec/factories/users.rb
FactoryBot.define do
  factory :user do
    # その他の属性

    trait :user_with_departments do
      after(:create) do |user, _|
        create(:department, :sales, user: user)
      end
    end
  end
end

使用例

pattern 1 を使う場合

userに紐付いたpostsが、after callback(after(:create))によって生成されます。

create(:user, posts_count: 1)

pattern 2 を使う場合

create(:user, :user_with_departments)

Fakerの使用

Fakerを使うと、ランダムなダミーデータを簡単に生成できます。これにより、テストデータに多様性を持たせることができます。

Fakerのインストール

Gemfileに以下の行を追加し、bundle installを実行します。

gem 'faker'

Fakerの使用例

以下は、Fakerを使用してユーザーデータを生成する例です。

# spec/factories/users.rb
FactoryBot.define do
  factory :user do
    name { Faker::Name.name }
    email { Faker::Internet.email }
    password { Faker::Internet.password(min_length: 8) }
  end
end
  • Faker::Name.nameでランダムな名前を生成します。
  • Faker::Internet.emailでランダムなメールアドレスを生成します。
  • Faker::Internet.password(min_length: 8)でランダムなパスワードを生成します。

まとめ

以下に、今回説明したFactory Botの主な特徴と使用シナリオをまとめました。

機能 説明 使用シナリオ
Factoryの定義 モデルごとにデフォルトの属性を持つテストデータを定義。 基本的なテストデータの生成。
traitの使用 特定の状態や属性を持つデータを追加で定義。 特定の条件下でのテストデータの生成。
コールバックの使用 オブジェクトの生成前後に特定の処理を実行。 関連データの生成や追加のセットアップが必要な場合。
Associationの使用 関連するオブジェクトを自動的に生成。 モデル間の関係性を考慮したテストデータの生成。
Fakerの使用 ランダムなダミーデータを生成。 テストデータに多様性を持たせたい場合。

Factory Botを使うことで、テストデータの生成が容易になり、テストコードの可読性とメンテナンス性が向上します。皆さんもぜひ、これらのテクニックを活用して、効率的なテストライティングを実現してください。

読んでいただき、ありがとうございました!

参考文献

8
1
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
8
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?