ActiveSupport::Testing::TimeHelpers を rspec-rails をロードする前に include すると travel_back が動かない
ActiveSupport::Testing::TimeHelpers
の travel_to
を使って日時を固定するテストで、
それ以降に実行する他のテストでも日時が固定されたままになっている現象に遭遇した。
# foo_spec.rb
describe 'foo' do
before { travel_to(Time.zone.now) }
# ...
end
# bar_spec.rb
describe 'bar' do
# 日時が固定されたまま
end
普通なら describe
や context
の単位で ActiveSupport::Testing::TimeHelpers
の after_teardown
が呼ばれて travel_back
が実行されるので、テストごとに固定が解除されるはず。
# https://github.com/rails/rails/blob/v7.0.4/activesupport/lib/active_support/testing/time_helpers.rb#L70-L73
def after_teardown
travel_back
super
end
RSpec の設定を確認すると spec_helper.rb
で ActiveSupport::Testing::TimeHelpers
を include
していた。
# spec/spec_helper.rb
RSpec.configure do |config|
require 'active_support/testing/time_helpers'
config.include ActiveSupport::Testing::TimeHelpers
# ...
これを rails_helper.rb
に移動すると、travel_back
が実行されるようになった。
# spec/rails_helper.rb
# This file is copied to spec/ when you run 'rails generate rspec:install'
require 'spec_helper'
ENV['RAILS_ENV'] ||= 'test'
require_relative '../config/environment'
# Prevent database truncation if the environment is production
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'rspec/rails'
# Add additional requires below this line. Rails is not loaded until this point!
RSpec.configure do |config|
config.include ActiveSupport::Testing::TimeHelpers # ここに移動
# ...
rspec-rails をロードすると after_teardown
が呼ばれる仕組み
Rails アプリで RSpec のテストを書く場合、だいたいは rspec-rails gem を使うと思う。
上記の rails_helper.rb
では rspec-rails をロード (require 'rspec/rails'
) した後に ActiveSupport::Testing::TimeHelpers
を include
した。
require 'rspec/rails'
すると Rails アプリのテストに便利な機能 (オフにしがちな use_transactional_fixtures
とか) や独自のアサーションの他に、Rails 標準のテスティングフレームワークである Minitest のアサーションやライフサイクルをサポートするモジュールやクラスがロードされる。
ロードされるモジュールのひとつである RSpec::Rails::MinitestLifecycleAdapter
によって、Minitest::Test::LifecycleHooks
と同等のフックがサポートされる。(これらのフックはライブラリから使うために用意されている)
# https://github.com/rspec/rspec-rails/blob/v5.1.2/lib/rspec/rails/adapters.rb#L76
group.around do |example|
before_setup
example.run
after_teardown
end
これが rspec-rails をロードした後に ActiveSupport::Testing::TimeHelpers
を include
すると after_teardown
のフックが呼び出される仕組みになっている。
余談だが、独自にフックするメソッドを作成する場合は、処理の後に super
を呼ばないと他のフックが呼び出されない。
def after_teardown
# 最後に super を呼ぶ
super
end
慣れていない人には分かりづらい spec_helper.rb
と rails_helper.rb
rails generate rspec:install
を実行すると spec_helper.rb
と rails_helper.rb
の 2 つが作成される。
それが慣れていない人にとってはどちらに書けば良いのか分かりづらいかもしれない。エラーになれば気付くことができるのだが、今回はとりあえず動くから気付くのが難しかった。
元々のコードは Active Support を require
する時点で違和感を覚えるのだが、レビューが漏れていたのもある。
慣習として (?) spec_helper.rb
には RSpec 独自の、rails_helper.rb
には Rails 独自の設定を書くことになっているらしいが、皆さんどうしているのだろうか。
僕は初期状態のまま spec_helper.rb
と rails_helper.rb
を分けている。