Skip to content

Commit 2a32c4b

Browse files
authored
Merge pull request rails#43734 from ghiculescu/ac-testcase-executor
Wrap `ActionController::TestCase` with Rails executor
2 parents 6f8aa2b + 5046d1c commit 2a32c4b

File tree

6 files changed

+143
-5
lines changed

6 files changed

+143
-5
lines changed

actionpack/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
* `Rails.application.executor` hooks can now be called around every request in a `ActionController::TestCase`
2+
3+
This helps to better simulate request or job local state being reset between requests and prevent state
4+
leaking from one request to another.
5+
6+
To enable this, set `config.active_support.executor_around_test_case = true` (this is the default in Rails 7).
7+
8+
*Alex Ghiculescu*
9+
110
* Consider onion services secure for cookies.
211

312
*Justin Tracey*

actionpack/lib/action_controller/railtie.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,5 +122,11 @@ class Railtie < Rails::Railtie # :nodoc:
122122
end
123123
end
124124
end
125+
126+
initializer "action_controller.test_case" do |app|
127+
ActiveSupport.on_load(:action_controller_test_case) do
128+
ActionController::TestCase.executor_around_each_request = app.config.active_support.executor_around_test_case
129+
end
130+
end
125131
end
126132
end

actionpack/lib/action_controller/test_case.rb

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,8 @@ def load!
333333
#
334334
# assert_redirected_to page_url(title: 'foo')
335335
class TestCase < ActiveSupport::TestCase
336+
class_attribute :executor_around_each_request, default: false
337+
336338
module Behavior
337339
extend ActiveSupport::Concern
338340
include ActionDispatch::TestProcess
@@ -578,10 +580,19 @@ def setup_request(controller_class_name, action, parameters, session, flash, xhr
578580
end
579581
end
580582

583+
def wrap_execution(&block)
584+
if executor_around_each_request && defined?(Rails.application) && Rails.application
585+
Rails.application.executor.wrap(&block)
586+
else
587+
yield
588+
end
589+
end
590+
581591
def process_controller_response(action, cookies, xhr)
582592
begin
583593
@controller.recycle!
584-
@controller.dispatch(action, @request, @response)
594+
595+
wrap_execution { @controller.dispatch(action, @request, @response) }
585596
ensure
586597
@request = @controller.request
587598
@response = @controller.response

activesupport/CHANGELOG.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@
2828

2929
*Sean Doyle*
3030

31-
* `Rails.application.executor` hooks are now called around every tests.
31+
* `Rails.application.executor` hooks can now be called around every test
3232

33-
This helps to better simulate request or job local state being reset around tests and prevent state
34-
to leak from one test to another.
33+
This helps to better simulate request or job local state being reset around tests and prevents state
34+
leaking from one test to another.
3535

3636
However it requires the executor hooks executed in the test environment to be re-entrant.
3737

38+
To enable this, set `config.active_support.executor_around_test_case = true` (this is the default in Rails 7).
39+
3840
*Jean Boussier*
3941

4042
* `ActiveSupport::DescendantsTracker` now mostly delegate to `Class#descendants` on Ruby 3.1

activesupport/lib/active_support/railtie.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class Railtie < Rails::Railtie # :nodoc:
4040
end
4141

4242
initializer "active_support.reset_all_current_attributes_instances" do |app|
43-
executor_around_test_case = app.config.active_support.delete(:executor_around_test_case)
43+
executor_around_test_case = app.config.active_support.executor_around_test_case
4444

4545
app.reloader.before_class_unload { ActiveSupport::CurrentAttributes.clear_all }
4646
app.executor.to_run { ActiveSupport::CurrentAttributes.reset_all }
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# frozen_string_literal: true
2+
3+
require "isolation/abstract_unit"
4+
require "rack/test"
5+
6+
class SharedSetup < ActionController::TestCase
7+
class_attribute :executor_around_each_request
8+
9+
include ActiveSupport::Testing::Isolation
10+
11+
setup do
12+
build_app
13+
14+
app_file "app/models/current.rb", <<-RUBY
15+
class Current < ActiveSupport::CurrentAttributes
16+
attribute :customer
17+
18+
resets { Time.zone = "UTC" }
19+
20+
def customer=(customer)
21+
super
22+
Time.zone = customer&.time_zone
23+
end
24+
end
25+
RUBY
26+
27+
app_file "app/models/customer.rb", <<-RUBY
28+
class Customer < Struct.new(:name)
29+
def time_zone
30+
"Copenhagen"
31+
end
32+
end
33+
RUBY
34+
35+
remove_from_config '.*config\.load_defaults.*\n'
36+
add_to_config "config.active_support.executor_around_test_case = #{self.class.executor_around_each_request}"
37+
38+
app_file "app/controllers/customers_controller.rb", <<-RUBY
39+
class CustomersController < ApplicationController
40+
layout false
41+
42+
def get_current_customer
43+
render :index
44+
end
45+
46+
def set_current_customer
47+
Current.customer = Customer.new("david")
48+
render :index
49+
end
50+
end
51+
RUBY
52+
53+
app_file "app/views/customers/index.html.erb", <<-RUBY
54+
<%= Current.customer&.name || 'noone' %>,<%= Time.zone.name %>
55+
RUBY
56+
57+
require "#{app_path}/config/environment"
58+
59+
@controller = CustomersController.new
60+
@routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
61+
r.draw do
62+
get "/customers/:action", controller: :customers
63+
end
64+
end
65+
end
66+
67+
teardown :teardown_app
68+
end
69+
70+
class ActionControllerTestCaseWithExecutorIntegrationTest < SharedSetup
71+
self.executor_around_each_request = true
72+
73+
test "current customer is cleared after each request" do
74+
assert Rails.application.config.active_support.executor_around_test_case
75+
assert ActionController::TestCase.executor_around_each_request
76+
77+
get :get_current_customer
78+
assert_response :ok
79+
assert_match(/noone,UTC/, response.body)
80+
81+
get :set_current_customer
82+
assert_response :ok
83+
assert_match(/david,Copenhagen/, response.body)
84+
85+
get :get_current_customer
86+
assert_response :ok
87+
assert_match(/noone,UTC/, response.body)
88+
end
89+
end
90+
91+
class ActionControllerTestCaseWithoutExecutorIntegrationTest < SharedSetup
92+
self.executor_around_each_request = false
93+
94+
test "current customer is not cleared after each request" do
95+
assert_not Rails.application.config.active_support.executor_around_test_case
96+
assert_not ActionController::TestCase.executor_around_each_request
97+
98+
get :get_current_customer
99+
assert_response :ok
100+
assert_match(/noone,UTC/, response.body)
101+
102+
get :set_current_customer
103+
assert_response :ok
104+
assert_match(/david,Copenhagen/, response.body)
105+
106+
get :get_current_customer
107+
assert_response :ok
108+
assert_match(/david,Copenhagen/, response.body)
109+
end
110+
end

0 commit comments

Comments
 (0)