Skip to content

Commit

Permalink
Implement RSpec.current_scope
Browse files Browse the repository at this point in the history
My reasoning is:

rspec-rails needs to do the following condition:
`if RSpec.current_scope == :before_context_hook`

test_prof needs to do the following condition:
`if RSpec.current_scope == :before_context_hook`

amd my little helper needs the following
`unless [:before_example_hook, :example].include?(RSpec.current_scope)`
  • Loading branch information
odinhb authored and pirj committed Jul 16, 2021
1 parent 91b7c0e commit 30efa04
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 @@ -6,6 +6,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
88 changes: 88 additions & 0 deletions features/metadata/current_scope.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
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
unless RSpec.current_scope == :suite
raise "bad current scope: #{RSpec.current_scope.inspect}"
end
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
25 changes: 25 additions & 0 deletions lib/rspec/core.rb
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,31 @@ 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 `:suite` while loading your tests (as soon as RSpec has loaded)
#
# Returns `:before_example_hook`/`:before_context_hook`/`:before_suite_hook`/
# `:after_example_hook`/`:after_context_hook`/`:after_suite_hook` in the respective hooks
#
# Returns `:before_example_hook`/`:after_example_hook` inside an `around :each` hook,
# before and after you call `example.run` respectively.
#
# Returns `:example` inside it/example blocks
#
# Returns `:suite` again after your suite and all hooks are done
# @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 @@ -2063,10 +2063,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 @@ -259,6 +259,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 @@ -278,6 +279,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 @@ -462,6 +464,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 @@ -608,6 +608,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 @@ -620,6 +621,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 @@ -129,6 +129,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 30efa04

Please sign in to comment.