Railsのフィーチャスペックでよく起きる問題
Railsのフィーチャスペックでjs: true
にする(テスト実行時にJavaScriptも動かす)場合、フィーチャスペック内で作成したテストデータがブラウザ(PoltergeistやSelenium webdriverで起動するFirefox)内で参照できない、といった問題がよく発生する。
以下は問題が発生するフィーチャスペックの例である。
require 'rails_helper'
feature 'User management' do
scenario "adds a new user", js: true do
admin = create(:admin)
visit root_path
click_link 'Log In'
fill_in 'Email', with: admin.email
fill_in 'Password', with: admin.password
# 上で作成したadminのデータがブラウザ側で参照できないのでログインに失敗する
click_button 'Log In'
# ...
end
end
この問題はテストを実行しているコードのDBコネクションと、フィーチャスペック用のWebサーバーが使うDBコネクションが別々であるため、一方のDBトランザクション内で作成されたデータは他方のDBコネクションから参照できないことに起因している。
従来の古い解決策(モンキーパッチあり)
この問題を解決するために、特殊なモンキーパッチとDatabaseCleanerを使って、以下のような解決策がよく取られていた。
class ActiveRecord::Base
mattr_accessor :shared_connection
@@shared_connection = nil
def self.connection
@@shared_connection || retrieve_connection
end
end
ActiveRecord::Base.shared_connection = ActiveRecord::Base.connection
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
RSpec.configure do |config|
# ...
config.use_transactional_fixtures = true
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with :truncation
end
config.around(:each) do |example|
DatabaseCleaner.cleaning do
example.run
end
end
config.after(:each) do
DatabaseCleaner.clean
end
end
これは何をやっているかというと、テスト実行コードとフィーチャスペック用のWebサーバーでDBコネクションを共有して、どちらからでも同じデータの読み書きをできるようにしている。
最近の解決策(モンキーパッチなし)
しかし、ActiveRecordにモンキーパッチを当てるというのは黒魔術的であまり気持ちがいいものではない。
そこで、最新のDatabaseCleanerのREADMEでは、上記のモンキーパッチを使わずにこの問題を解決する方法が載っている。
# 不要になるのでファイルごと削除
RSpec.configure do |config|
config.use_transactional_fixtures = false
config.before(:suite) do
if config.use_transactional_fixtures?
raise(<<-MSG)
設定がおかしいので警告メッセージを表示 (省略)
MSG
end
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.strategy = :transaction
end
config.before(:each, type: :feature) do
driver_shares_db_connection_with_specs = Capybara.current_driver == :rack_test
if !driver_shares_db_connection_with_specs
DatabaseCleaner.strategy = :truncation
end
end
config.before(:each) do
DatabaseCleaner.start
end
config.append_after(:each) do
DatabaseCleaner.clean
end
end
上記の設定は簡単にいうと次のようになっている。
-
js: true
の場合(driver_shares_db_connection_with_specs
がfalse
の場合)はトランザクションを使わずにデータを読み書きする。トランザクションを使わないので、どのDB接続からでも同じデータを参照できる。テストが終わったら全データをtruncateする。 - それ以外の場合はDB接続は1つしかないので、トランザクション内でデータを読み書きする。
動作確認した実行環境
筆者は以下の環境で「モンキーパッチを使わない解決策」が有効に機能することを確認した。
古いバージョンのgemでは確認していないので、この解決策を適用する場合はテスト関連のgemはなるべく最新にすることが望ましい。
- Rails 5.0.0
- rspec-rails 3.5.0
- capybara 2.7.1
- database_cleaner 1.5.3
- selenium-webdriver 2.53.4
- factory_girl_rails 4.7.0
サンプルコード
Everyday Rails - RSpecによるRailsテスト入門のサンプルアプリケーションをベースに上記の解決策を適用してみた。
テストコードの内容は以下のGitHubリポジトリで確認できる。
この設定が解決するかもしれないトラブル
Rails 5にアップデートすると、リクエストスペックで次のようなエラーが発生してテストが落ちる場合がある。
PG::ConnectionBad: PQsocket() can't get socket descriptor: ROLLBACK TO SAVEPOINT active_record_1
テスト関連のgemを最新にし、モンキーパッチを使わない解決策を適用したところ、この問題が解消した。
その他:Everyday Railsの読者のみなさんへ
Everyday Rails - RSpecによるRailsテスト入門では「従来の古い解決策」が載っているので、適宜「モンキーパッチを使わない解決策」を使ってやってください。
具体的なスケジュールは未定ですが、Everyday RailsもRails 5に対応する予定なので、そのときには最新の解決策が載っているはずです
あわせて読みたい
Everyday RailsのサンプルアプリケーションをRails 5.0にアップグレードする方法を解説した記事です。
こちらもあわせてどうぞ。
これでもう怖くない!?Rails 4.1からRails 5.0にアップグレードする手順を動画付きで解説します - Qiita