Skip to content
This repository was archived by the owner on Nov 30, 2024. It is now read-only.

Commit fe497cc

Browse files
committed
Merge pull request #2895 from odinhb/current_scope
Add RSpec.current_scope method to replace `currently_executing_a_context_hook?` and `self.inspect` hack
1 parent d116291 commit fe497cc

File tree

8 files changed

+138
-1
lines changed

8 files changed

+138
-1
lines changed

.rubocop.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Metrics/LineLength:
2424

2525
# This should go down over time.
2626
Metrics/MethodLength:
27-
Max: 37
27+
Max: 39
2828

2929
# This should go down over time.
3030
Metrics/CyclomaticComplexity:

Changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ Enhancements:
3939
* Improve pluralisation of words ending with `s` (like process). (Joshua Pinter, #2779)
4040
* Add ordering by file modification time (most recent first). (Matheus Richard, #2778)
4141
* Add `to_s` to reserved names for #let and #subject. (Nick Flückiger, #2886)
42+
* Introduce `RSpec.current_scope` to expose the current scope in which
43+
RSpec is executing. e.g. `:before_example_hook`, `:example` etc. (@odinhb, #2895)
4244

4345
Bug fixes:
4446

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
Feature: RSpec provides the current scope as RSpec.current_scope
2+
3+
You can detect which rspec scope your helper methods or library code is executing in.
4+
This is useful if for example, your method only makes sense to call in a certain context.
5+
6+
Scenario: Detecting the current scope
7+
Given a file named "current_scope_spec.rb" with:
8+
"""ruby
9+
# Outside of the test lifecycle, the current scope is `:suite`
10+
exit(1) unless RSpec.current_scope == :suite
11+
12+
at_exit do
13+
exit(1) unless RSpec.current_scope == :suite
14+
end
15+
16+
RSpec.configure do |c|
17+
c.before :suite do
18+
expect(RSpec.current_scope).to eq(:before_suite_hook)
19+
end
20+
21+
c.before :context do
22+
expect(RSpec.current_scope).to eq(:before_context_hook)
23+
end
24+
25+
c.before :example do
26+
expect(RSpec.current_scope).to eq(:before_example_hook)
27+
end
28+
29+
c.around :example do |ex|
30+
expect(RSpec.current_scope).to eq(:before_example_hook)
31+
ex.run
32+
expect(RSpec.current_scope).to eq(:after_example_hook)
33+
end
34+
35+
c.after :example do
36+
expect(RSpec.current_scope).to eq(:after_example_hook)
37+
end
38+
39+
c.after :context do
40+
expect(RSpec.current_scope).to eq(:after_context_hook)
41+
end
42+
43+
c.after :suite do
44+
expect(RSpec.current_scope).to eq(:after_suite_hook)
45+
end
46+
end
47+
48+
RSpec.describe "RSpec.current_scope" do
49+
before :context do
50+
expect(RSpec.current_scope).to eq(:before_context_hook)
51+
end
52+
53+
before :example do
54+
expect(RSpec.current_scope).to eq(:before_example_hook)
55+
end
56+
57+
around :example do |ex|
58+
expect(RSpec.current_scope).to eq(:before_example_hook)
59+
ex.run
60+
expect(RSpec.current_scope).to eq(:after_example_hook)
61+
end
62+
63+
after :example do
64+
expect(RSpec.current_scope).to eq(:after_example_hook)
65+
end
66+
67+
after :context do
68+
expect(RSpec.current_scope).to eq(:after_context_hook)
69+
end
70+
71+
it "is :example in an example" do
72+
expect(RSpec.current_scope).to eq(:example)
73+
end
74+
75+
it "works for multiple examples" do
76+
expect(RSpec.current_scope).to eq(:example)
77+
end
78+
79+
describe "in nested describe blocks" do
80+
it "still works" do
81+
expect(RSpec.current_scope).to eq(:example)
82+
end
83+
end
84+
end
85+
"""
86+
When I run `rspec current_scope_spec.rb`
87+
Then the examples should all pass

lib/rspec/core.rb

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,32 @@ def self.current_example=(example)
111111
RSpec::Support.thread_local_data[:current_example] = example
112112
end
113113

114+
# Set the current scope rspec is executing in
115+
# @api private
116+
def self.current_scope=(scope)
117+
RSpec::Support.thread_local_data[:current_scope] = scope
118+
end
119+
RSpec.current_scope = :suite
120+
121+
# Get the current RSpec execution scope
122+
#
123+
# Returns (in order of lifecycle):
124+
# * `:suite` as an initial value, this is outside of the test lifecycle.
125+
# * `:before_suite_hook` during `before(:suite)` hooks.
126+
# * `:before_context_hook` during `before(:context)` hooks.
127+
# * `:before_example_hook` during `before(:example)` hooks and `around(:example)` before `example.run`.
128+
# * `:example` within the example run.
129+
# * `:after_example_hook` during `after(:example)` hooks and `around(:example)` after `example.run`.
130+
# * `:after_context_hook` during `after(:context)` hooks.
131+
# * `:after_suite_hook` during `after(:suite)` hooks.
132+
# * `:suite` as a final value, again this is outside of the test lifecycle.
133+
#
134+
# Reminder, `:context` hooks have `:all` alias and `:example` hooks have `:each` alias.
135+
# @return [Symbol]
136+
def self.current_scope
137+
RSpec::Support.thread_local_data[:current_scope]
138+
end
139+
114140
# @private
115141
# Internal container for global non-configuration data.
116142
def self.world

lib/rspec/core/configuration.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1886,10 +1886,13 @@ def with_suite_hooks
18861886
return yield if dry_run?
18871887

18881888
begin
1889+
RSpec.current_scope = :before_suite_hook
18891890
run_suite_hooks("a `before(:suite)` hook", @before_suite_hooks)
18901891
yield
18911892
ensure
1893+
RSpec.current_scope = :after_suite_hook
18921894
run_suite_hooks("an `after(:suite)` hook", @after_suite_hooks)
1895+
RSpec.current_scope = :suite
18931896
end
18941897
end
18951898

lib/rspec/core/example.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ def run(example_group_instance, reporter)
252252
with_around_and_singleton_context_hooks do
253253
begin
254254
run_before_example
255+
RSpec.current_scope = :example
255256
@example_group_instance.instance_exec(self, &@example_block)
256257

257258
if pending?
@@ -271,6 +272,7 @@ def run(example_group_instance, reporter)
271272
rescue AllExceptionsExcludingDangerousOnesOnRubiesThatAllowIt => e
272273
set_exception(e)
273274
ensure
275+
RSpec.current_scope = :after_example_hook
274276
run_after_example
275277
end
276278
end
@@ -455,6 +457,7 @@ def hooks
455457
end
456458

457459
def with_around_example_hooks
460+
RSpec.current_scope = :before_example_hook
458461
hooks.run(:around, :example, self) { yield }
459462
rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e
460463
set_exception(e)

lib/rspec/core/example_group.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,7 @@ def self.run(reporter=RSpec::Core::NullReporter)
580580

581581
should_run_context_hooks = descendant_filtered_examples.any?
582582
begin
583+
RSpec.current_scope = :before_context_hook
583584
run_before_context_hooks(new('before(:context) hook')) if should_run_context_hooks
584585
result_for_this_group = run_examples(reporter)
585586
results_for_descendants = ordering_strategy.order(children).map { |child| child.run(reporter) }.all?
@@ -592,6 +593,7 @@ def self.run(reporter=RSpec::Core::NullReporter)
592593
RSpec.world.wants_to_quit = true if reporter.fail_fast_limit_met?
593594
false
594595
ensure
596+
RSpec.current_scope = :after_context_hook
595597
run_after_context_hooks(new('after(:context) hook')) if should_run_context_hooks
596598
reporter.example_group_finished(self)
597599
end

spec/rspec/core_spec.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,20 @@
119119
end
120120
end
121121

122+
describe ".current_scope" do
123+
before :context do
124+
expect(RSpec.current_scope).to eq(:before_context_hook)
125+
end
126+
127+
before do
128+
expect(RSpec.current_scope).to eq(:before_example_hook)
129+
end
130+
131+
it "returns :example inside an example" do
132+
expect(RSpec.current_scope).to eq(:example)
133+
end
134+
end
135+
122136
describe ".reset" do
123137
it "resets the configuration and world objects" do
124138
config_before_reset = RSpec.configuration

0 commit comments

Comments
 (0)