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
endRSpec の設定を確認すると 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 を分けている。