Edited at

FactoryGirl.lint で valid なテストデータを作れているかチェックする

More than 1 year has passed since last update.

この記事では、RSpec + factory_girl な環境で、factory の定義がモデルの validation に対して適切になっているかチェックする方法を紹介します。

例えば、あるモデルについて新しい factory を追加する場合に、定義が誤っていて、テストデータ自体が ActiveRecord モデルの validation を通らないことが起こりえます。このとき、そのテストデータを使っているテスト実行時に大量にエラーが出たりすると、原因の切り分けが面倒になるかもしれません。

ここで、factory_girl には lint 機能が用意されています。この機能を使うと、各 factory を実行して、データ作成時になにか例外が起こると FactoryGirl::InvalidFactoryError を投げてエラーを検出してくれます。これを RSpec のテスト実行前に一度だけ実行しておくと、factory 自体に問題があることがすぐにわかるようになります。


実行例

実際に lint でエラーが検出される例を示します。

次のモデルがあるとします。


app/models/user.rb

class User < ApplicationRecord

validates :name, presence: true
validates :email, presence: true, uniqueness: true

# ...
end


このモデルに対して、次の factory を定義したとします。ここでは、わざと同じ email しか生成しないようにしています。


spec/factories/users.rb

FactoryGirl.define do

factory :user do
name { FFaker::Name.name }
email 'fixed@example.com'
end
end

この後の「導入方法」で説明する方法を実行したあとに RSpec を実行すると、FactoryGirl.lint によって次のようなエラーが表示されます。

$ bundle exec rake

rake aborted!
FactoryGirl::InvalidFactoryError: The following factories are invalid:

* user - Validation failed: Email has already been taken (ActiveRecord::RecordInvalid)
# 以下スタックトレース

このようなエラーが出たならば、factory の定義がおかしいということになるので、そちらを修正すればよいということがすぐにわかります。


導入方法

この記事で利用するツールのバージョンは次のとおりです。


  • rspec-rails v3.5.2

  • factory_girl_rails v4.8.0


gem の追加

factory_girl は導入済みとします。別途、database_rewinder が導入済みでないなら導入します。


Gemfile

group :development, :test do

# ...
gem 'database_rewinder'
end


lint 実行用タスクの追加

次に、普通のテスト実行前に lint を実行する Rake タスクを作ります。


lib/tasks/spec_with_lint.rake

desc 'Run all specs in spec directory with factory linting (excluding plugin specs)'

task spec_with_lint: :environment do
if Rails.env.test?
begin
FactoryGirl.lint trait: true
ensure
DatabaseRewinder.clean_all
end
Rake::Task[:spec].invoke
else
system "bundle exec rake spec_with_lint RAILS_ENV='test'"
end
end

FactoryGirl.lint の実行時に traits: true を指定すると、factory だけではなく trait に関しても lint を実行できます。また、lint が成功したときとエラーを検出したときの両方で lint 時に作成したレコードを確実に削除しています。FactoryGirl.lint の実行が終わったあと、rspec-rails デフォルトの spec タスクをキックして、各テストを実行しています。


デフォルトタスク化

最後に、rake を引数なしで叩いたときに実行するデフォルトのタスクを上で定義した spec_with_lint にします。

ただし、FactoryGirl.lint は引数で factory を指定しない限り、すべての factory についてひととおりデータを作成します。このため、factory の数が増えると、一つの単体テストだけ実行したいときなどに速度の低下が無視できなくなってくると思います。そこで、lint なしのテストをするタスク spec を実行したいときは rake spec を叩けばよいようにします。

やることは Rakefile で Rails 用タスクをロードする前にデフォルトタスクを上書きするだけです。


Rakefile

# Add your own tasks in files placed in lib/tasks ending in .rake,

# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.

require_relative 'config/application'

task default: :spec_with_lint # 追加

Rails.application.load_tasks


これで、次のコマンドで lint ありテストを実行できます。

$ bundle exec rake

また、次のようなコマンドで lint なしテストを実行できます。

$ bundle exec rake spec spec/models/user_spec.rb:10 # イメージ


参考資料