この記事では、RSpec + factory_girl な環境で、factory の定義がモデルの validation に対して適切になっているかチェックする方法を紹介します。
例えば、あるモデルについて新しい factory を追加する場合に、定義が誤っていて、テストデータ自体が ActiveRecord モデルの validation を通らないことが起こりえます。このとき、そのテストデータを使っているテスト実行時に大量にエラーが出たりすると、原因の切り分けが面倒になるかもしれません。
ここで、factory_girl には lint 機能が用意されています。この機能を使うと、各 factory を実行して、データ作成時になにか例外が起こると FactoryGirl::InvalidFactoryError
を投げてエラーを検出してくれます。これを RSpec のテスト実行前に一度だけ実行しておくと、factory 自体に問題があることがすぐにわかるようになります。
実行例
実際に lint でエラーが検出される例を示します。
次のモデルがあるとします。
class User < ApplicationRecord
validates :name, presence: true
validates :email, presence: true, uniqueness: true
# ...
end
このモデルに対して、次の factory を定義したとします。ここでは、わざと同じ email
しか生成しないようにしています。
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 が導入済みでないなら導入します。
group :development, :test do
# ...
gem 'database_rewinder'
end
lint 実行用タスクの追加
次に、普通のテスト実行前に 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 用タスクをロードする前にデフォルトタスクを上書きするだけです。
# 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 # イメージ