Skip to content

Commit

Permalink
Merge pull request #2895 from odinhb/current_scope
Browse files Browse the repository at this point in the history
Add RSpec.current_scope method to replace `currently_executing_a_context_hook?` and `self.inspect` hack
  • Loading branch information
pirj committed Aug 14, 2021
1 parent d116291 commit fe497cc
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Metrics/LineLength:

# This should go down over time.
Metrics/MethodLength:
Max: 37
Max: 39

# This should go down over time.
Metrics/CyclomaticComplexity:
Expand Down
2 changes: 2 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ Enhancements:
* Improve pluralisation of words ending with `s` (like process). (Joshua Pinter, #2779)
* Add ordering by file modification time (most recent first). (Matheus Richard, #2778)
* Add `to_s` to reserved names for #let and #subject. (Nick Flückiger, #2886)
* Introduce `RSpec.current_scope` to expose the current scope in which
RSpec is executing. e.g. `:before_example_hook`, `:example` etc. (@odinhb, #2895)

Bug fixes:

Expand Down
87 changes: 87 additions & 0 deletions features/metadata/current_scope.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
Feature: RSpec provides the current scope as RSpec.current_scope

You can detect which rspec scope your helper methods or library code is executing in.
This is useful if for example, your method only makes sense to call in a certain context.

Scenario: Detecting the current scope
Given a file named "current_scope_spec.rb" with:
"""ruby
# Outside of the test lifecycle, the current scope is `:suite`
exit(1) unless RSpec.current_scope == :suite
at_exit do
exit(1) unless RSpec.current_scope == :suite
end
RSpec.configure do |c|
c.before :suite do
expect(RSpec.current_scope).to eq(:before_suite_hook)
end
c.before :context do
expect(RSpec.current_scope).to eq(:before_context_hook)
end
c.before :example do
expect(RSpec.current_scope).to eq(:before_example_hook)
end
c.around :example do |ex|
expect(RSpec.current_scope).to eq(:before_example_hook)
ex.run
expect(RSpec.current_scope).to eq(:after_example_hook)
end
c.after :example do
expect(RSpec.current_scope).to eq(:after_example_hook)
end
c.after :context do
expect(RSpec.current_scope).to eq(:after_context_hook)
end
c.after :suite do
expect(RSpec.current_scope).to eq(:after_suite_hook)
end
end
RSpec.describe "RSpec.current_scope" do
before :context do
expect(RSpec.current_scope).to eq(:before_context_hook)
end
before :example do
expect(RSpec.current_scope).to eq(:before_example_hook)
end
around :example do |ex|
expect(RSpec.current_scope).to eq(:before_example_hook)
ex.run
expect(RSpec.current_scope).to eq(:after_example_hook)
end
after :example do
expect(RSpec.current_scope).to eq(:after_example_hook)
end
after :context do
expect(RSpec.current_scope).to eq(:after_context_hook)
end
it "is :example in an example" do
expect(RSpec.current_scope).to eq(:example)
end
it "works for multiple examples" do
expect(RSpec.current_scope).to eq(:example)
end
describe "in nested describe blocks" do
it "still works" do
expect(RSpec.current_scope).to eq(:example)
end
end
end
"""
When I run `rspec current_scope_spec.rb`
Then the examples should all pass
26 changes: 26 additions & 0 deletions lib/rspec/core.rb
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,32 @@ def self.current_example=(example)
RSpec::Support.thread_local_data[:current_example] = example
end

# Set the current scope rspec is executing in
# @api private
def self.current_scope=(scope)
RSpec::Support.thread_local_data[:current_scope] = scope
end
RSpec.current_scope = :suite

# Get the current RSpec execution scope
#
# Returns (in order of lifecycle):
# * `:suite` as an initial value, this is outside of the test lifecycle.
# * `:before_suite_hook` during `before(:suite)` hooks.
# * `:before_context_hook` during `before(:context)` hooks.
# * `:before_example_hook` during `before(:example)` hooks and `around(:example)` before `example.run`.
# * `:example` within the example run.
# * `:after_example_hook` during `after(:example)` hooks and `around(:example)` after `example.run`.
# * `:after_context_hook` during `after(:context)` hooks.
# * `:after_suite_hook` during `after(:suite)` hooks.
# * `:suite` as a final value, again this is outside of the test lifecycle.
#
# Reminder, `:context` hooks have `:all` alias and `:example` hooks have `:each` alias.
# @return [Symbol]
def self.current_scope
RSpec::Support.thread_local_data[:current_scope]
end

# @private
# Internal container for global non-configuration data.
def self.world
Expand Down
3 changes: 3 additions & 0 deletions lib/rspec/core/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1886,10 +1886,13 @@ def with_suite_hooks
return yield if dry_run?

begin
RSpec.current_scope = :before_suite_hook
run_suite_hooks("a `before(:suite)` hook", @before_suite_hooks)
yield
ensure
RSpec.current_scope = :after_suite_hook
run_suite_hooks("an `after(:suite)` hook", @after_suite_hooks)
RSpec.current_scope = :suite
end
end

Expand Down
3 changes: 3 additions & 0 deletions lib/rspec/core/example.rb
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ def run(example_group_instance, reporter)
with_around_and_singleton_context_hooks do
begin
run_before_example
RSpec.current_scope = :example
@example_group_instance.instance_exec(self, &@example_block)

if pending?
Expand All @@ -271,6 +272,7 @@ def run(example_group_instance, reporter)
rescue AllExceptionsExcludingDangerousOnesOnRubiesThatAllowIt => e
set_exception(e)
ensure
RSpec.current_scope = :after_example_hook
run_after_example
end
end
Expand Down Expand Up @@ -455,6 +457,7 @@ def hooks
end

def with_around_example_hooks
RSpec.current_scope = :before_example_hook
hooks.run(:around, :example, self) { yield }
rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e
set_exception(e)
Expand Down
2 changes: 2 additions & 0 deletions lib/rspec/core/example_group.rb
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,7 @@ def self.run(reporter=RSpec::Core::NullReporter)

should_run_context_hooks = descendant_filtered_examples.any?
begin
RSpec.current_scope = :before_context_hook
run_before_context_hooks(new('before(:context) hook')) if should_run_context_hooks
result_for_this_group = run_examples(reporter)
results_for_descendants = ordering_strategy.order(children).map { |child| child.run(reporter) }.all?
Expand All @@ -592,6 +593,7 @@ def self.run(reporter=RSpec::Core::NullReporter)
RSpec.world.wants_to_quit = true if reporter.fail_fast_limit_met?
false
ensure
RSpec.current_scope = :after_context_hook
run_after_context_hooks(new('after(:context) hook')) if should_run_context_hooks
reporter.example_group_finished(self)
end
Expand Down
14 changes: 14 additions & 0 deletions spec/rspec/core_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,20 @@
end
end

describe ".current_scope" do
before :context do
expect(RSpec.current_scope).to eq(:before_context_hook)
end

before do
expect(RSpec.current_scope).to eq(:before_example_hook)
end

it "returns :example inside an example" do
expect(RSpec.current_scope).to eq(:example)
end
end

describe ".reset" do
it "resets the configuration and world objects" do
config_before_reset = RSpec.configuration
Expand Down

0 comments on commit fe497cc

Please sign in to comment.