diff --git a/lib/rspec/rails/adapters.rb b/lib/rspec/rails/adapters.rb index 74bee1c85..413e8b18d 100644 --- a/lib/rspec/rails/adapters.rb +++ b/lib/rspec/rails/adapters.rb @@ -136,6 +136,24 @@ def method_name end end + # @private + # + # Reset ActiveSupport::CurrentAttributes only *after* tests run, rather + # than before *and* after. + # + # See https://github.com/rspec/rspec-rails/pull/2774 for more details. + # + module ActiveSupportCurrentAttributesAdapter + extend ActiveSupport::Concern + + included do + def after_teardown + super + ActiveSupport::CurrentAttributes.reset_all + end + end + end + # @private module MinitestAssertionAdapter extend ActiveSupport::Concern diff --git a/lib/rspec/rails/example/rails_example_group.rb b/lib/rspec/rails/example/rails_example_group.rb index ed343de35..196666d8b 100644 --- a/lib/rspec/rails/example/rails_example_group.rb +++ b/lib/rspec/rails/example/rails_example_group.rb @@ -19,7 +19,7 @@ module RailsExampleGroup include RSpec::Rails::FixtureSupport if ::Rails::VERSION::MAJOR >= 7 include RSpec::Rails::TaggedLoggingAdapter - include ActiveSupport::CurrentAttributes::TestHelper + include RSpec::Rails::ActiveSupportCurrentAttributesAdapter include ActiveSupport::ExecutionContext::TestHelper end end diff --git a/spec/rspec/rails/example/rails_example_group_spec.rb b/spec/rspec/rails/example/rails_example_group_spec.rb index 7dbe13788..1a55c4c94 100644 --- a/spec/rspec/rails/example/rails_example_group_spec.rb +++ b/spec/rspec/rails/example/rails_example_group_spec.rb @@ -1,3 +1,5 @@ +require 'rspec/support/spec/in_sub_process' + module RSpec::Rails RSpec.describe RailsExampleGroup do it 'supports tagged_logger', if: ::Rails::VERSION::MAJOR >= 7 do @@ -56,5 +58,83 @@ class CurrentSample < ActiveSupport::CurrentAttributes group.run(failure_reporter) ? true : failure_reporter.exceptions ).to be true end + + context 'with suite-level around-example hooks configured', if: ::Rails::VERSION::MAJOR >= 7 do + + # rubocop:disable Lint/ConstantDefinitionInBlock + class CurrentAttrsBetweenHooks < ActiveSupport::CurrentAttributes + attribute :request_id + end + # rubocop:enable Lint/ConstantDefinitionInBlock + + # This dirties global state, so tests *MUST* remember to use + # "in_sub_process". + # + def configure_rspec_to_set_current_attrs_before_around_example + # + # Client code might legitimately want to wrap examples to ensure + # all-conditions tidy-up, e.g. "ActsAsTenant.without_tenant do...", + # wherein an "around" hook is the only available solution, often used + # in the overall suite via "config.around". Tests would not expect + # anything set in CurrentAttributes here to suddenly be reset by the + # time their actual tests, or their test hooks ran. + # + RSpec.configure do |config| + config.around(:each) do |example| + expect(CurrentAttrsBetweenHooks.request_id).to be_nil + CurrentAttrsBetweenHooks.request_id = '123' + example.run + end + end + end + + it 'does not reset ActiveSupport::CurrentAttributes before examples' do + in_sub_process do + configure_rspec_to_set_current_attrs_before_around_example + + group = + RSpec::Core::ExampleGroup.describe('A group') do + include RSpec::Rails::RailsExampleGroup + + it 'runs normally' do + expect(CurrentAttrsBetweenHooks.request_id).to eq('123') + end + end + + expect( + group.run(failure_reporter) ? true : failure_reporter.exceptions + ).to be true + end + end + + it 'does not reset ActiveSupport::CurrentAttributes before before-each hooks' do + in_sub_process do + configure_rspec_to_set_current_attrs_before_around_example + + group = + RSpec::Core::ExampleGroup.describe('A group') do + include RSpec::Rails::RailsExampleGroup + + # Client code will often have test setup blocks within "*_spec.rb" + # files that might set up data or other environmental factors for a + # group of tests in e.g. a "before" hook, but would reasonably expect + # suite-wide 'around' settings to remain intact and not be reset. + # + before :each do + expect(CurrentAttrsBetweenHooks.request_id).to eq('123') + CurrentAttrsBetweenHooks.request_id = '234' + end + + it 'runs normally' do + expect(CurrentAttrsBetweenHooks.request_id).to eq('234') + end + end + + expect( + group.run(failure_reporter) ? true : failure_reporter.exceptions + ).to be true + end + end + end end end