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

Commit ed2d59c

Browse files
committed
Provide config option for shared context metadata behavior.
Previously, it always triggered auto-inclusion based on matching metadata. The option allows you to opt-in to having it add the metadata to included groups and examples instead. - Closes #1790 (this is the last thing necessary for it). - Addresses #1762. - Addresses user confusion reported in: - rspec/rspec-rails#1241 - rspec/rspec-rails#1579
1 parent 9ca21ab commit ed2d59c

File tree

7 files changed

+253
-35
lines changed

7 files changed

+253
-35
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: 40
27+
Max: 37
2828

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

Changelog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ Enhancements:
1616
* Add new `config.include_context` API for configuring global or
1717
filtered inclusion of shared contexts in example groups.
1818
(Myron Marston, #2256)
19+
* Add new `config.shared_context_metadata_behavior = :apply_to_host_groups`
20+
option, which causes shared context metadata to be inherited by the
21+
metadata hash of all host groups and examples instead of configuring
22+
implicit auto-inclusion based on the passed metadata. (Myron Marston, #2256)
1923

2024
Bug Fixes:
2125

features/example_groups/shared_context.feature

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,17 @@ Feature: shared context
77
Background:
88
Given a file named "shared_stuff.rb" with:
99
"""ruby
10-
RSpec.shared_context "shared stuff" do
10+
RSpec.configure do |rspec|
11+
# This config option will be enabled by default on RSpec 4,
12+
# but for reasons of backwards compatibility, you have to
13+
# set it on RSpec 3.
14+
#
15+
# It causes the host group and examples to inherit metadata
16+
# from the shared context.
17+
rspec.shared_context_metadata_behavior = :apply_to_host_groups
18+
end
19+
20+
RSpec.shared_context "shared stuff", :shared_context => :metadata do
1121
before { @some_var = :some_value }
1222
def shared_method
1323
"it works"
@@ -46,6 +56,13 @@ Feature: shared context
4656
it "accesses the subject defined in the shared context" do
4757
expect(subject).to eq('this is the subject')
4858
end
59+
60+
group = self
61+
62+
it "inherits metadata from the included context" do |ex|
63+
expect(group.metadata).to include(:shared_context => :metadata)
64+
expect(ex.metadata).to include(:shared_context => :metadata)
65+
end
4966
end
5067
"""
5168
When I run `rspec shared_context_example.rb`
@@ -90,6 +107,13 @@ Feature: shared context
90107
it "accesses the subject defined in the shared context" do
91108
expect(subject).to eq('this is the subject')
92109
end
110+
111+
group = self
112+
113+
it "inherits metadata from the included context" do |ex|
114+
expect(group.metadata).to include(:shared_context => :metadata)
115+
expect(ex.metadata).to include(:shared_context => :metadata)
116+
end
93117
end
94118
"""
95119
When I run `rspec shared_context_example.rb`
@@ -108,6 +132,10 @@ Feature: shared context
108132
it "has access to shared methods from examples with matching metadata", :include_shared => true do
109133
expect(shared_method).to eq("it works")
110134
end
135+
136+
it "inherits metadata form the included context due to the matching metadata", :include_shared => true do |ex|
137+
expect(ex.metadata).to include(:shared_context => :metadata)
138+
end
111139
end
112140
"""
113141
When I run `rspec shared_context_example.rb`

lib/rspec/core/configuration.rb

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,59 @@ def treat_symbols_as_metadata_keys_with_true_values=(_value)
327327
)
328328
end
329329

330+
# @macro define_reader
331+
# Configures how RSpec treats metadata passed as part of a shared example
332+
# group definition. For example, given this shared example group definition:
333+
#
334+
# RSpec.shared_context "uses DB", :db => true do
335+
# around(:example) do |ex|
336+
# MyORM.transaction(:rollback => true, &ex)
337+
# end
338+
# end
339+
#
340+
# ...there are two ways RSpec can treat the `:db => true` metadata, each
341+
# of which has a corresponding config option:
342+
#
343+
# 1. `:trigger_inclusion`: this shared context will be implicitly included
344+
# in any groups (or examples) that have `:db => true` metadata.
345+
# 2. `:apply_to_host_groups`: the metadata will be inherited by the metadata
346+
# hash of all host groups and examples.
347+
#
348+
# `:trigger_inclusion` is the legacy behavior from before RSpec 3.5 but should
349+
# be considered deprecated. Instead, you can explicitly include a group with
350+
# `include_context`:
351+
#
352+
# RSpec.describe "My model" do
353+
# include_context "uses DB"
354+
# end
355+
#
356+
# ...or you can configure RSpec to include the context based on matching metadata
357+
# using an API that mirrors configured module inclusion:
358+
#
359+
# RSpec.configure do |rspec|
360+
# rspec.include_context "uses DB", :db => true
361+
# end
362+
#
363+
# `:apply_to_host_groups` is a new feature of RSpec 3.5 and will be the only
364+
# supported behavior in RSpec 4.
365+
#
366+
# @overload shared_context_metadata_behavior
367+
# @return [:trigger_inclusion, :apply_to_host_groups] the configured behavior
368+
# @overload shared_context_metadata_behavior=(value)
369+
# @param value [:trigger_inclusion, :apply_to_host_groups] sets the configured behavior
370+
define_reader :shared_context_metadata_behavior
371+
# @see shared_context_metadata_behavior
372+
def shared_context_metadata_behavior=(value)
373+
case value
374+
when :trigger_inclusion, :apply_to_host_groups
375+
@shared_context_metadata_behavior = value
376+
else
377+
raise ArgumentError, "Cannot set `RSpec.configuration." \
378+
"shared_context_metadata_behavior` to `#{value.inspect}`. Only " \
379+
"`:trigger_inclusion` and `:apply_to_host_groups` are valid values."
380+
end
381+
end
382+
330383
# Record the start time of the spec suite to measure load time.
331384
add_setting :start_time
332385

@@ -352,6 +405,7 @@ def treat_symbols_as_metadata_keys_with_true_values=(_value)
352405
attr_reader :backtrace_formatter, :ordering_manager, :loaded_spec_files
353406

354407
# rubocop:disable Metrics/AbcSize
408+
# rubocop:disable Metrics/MethodLength
355409
def initialize
356410
# rubocop:disable Style/GlobalVars
357411
@start_time = $_rspec_core_load_started_at || ::RSpec::Core::Time.now
@@ -398,9 +452,11 @@ def initialize
398452
@threadsafe = true
399453
@max_displayed_failure_line_count = 10
400454
@world = World::Null
455+
@shared_context_metadata_behavior = :trigger_inclusion
401456

402457
define_built_in_hooks
403458
end
459+
# rubocop:enable Metrics/MethodLength
404460
# rubocop:enable Metrics/AbcSize
405461

406462
# @private

lib/rspec/core/shared_example_group.rb

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ class SharedExampleGroupModule < Module
88
# @private
99
attr_reader :definition
1010

11-
def initialize(description, definition)
11+
def initialize(description, definition, metadata)
1212
@description = description
1313
@definition = definition
14+
@metadata = metadata
1415
end
1516

1617
# Provides a human-readable representation of this module.
@@ -29,6 +30,8 @@ def included(klass)
2930

3031
# @private
3132
def include_in(klass, inclusion_line, args, customization_block)
33+
klass.update_inherited_metadata(@metadata) unless @metadata.empty?
34+
3235
SharedExampleGroupInclusionStackFrame.with_frame(@description, inclusion_line) do
3336
klass.class_exec(*args, &@definition)
3437
klass.class_exec(&customization_block) if customization_block
@@ -150,18 +153,21 @@ def self.remove_globally!
150153
# @private
151154
class Registry
152155
def add(context, name, *metadata_args, &block)
153-
ensure_block_has_source_location(block) { CallerFilter.first_non_rspec_line }
154-
shared_module = SharedExampleGroupModule.new(name, block)
156+
if RSpec.configuration.shared_context_metadata_behavior == :trigger_inclusion
157+
return legacy_add(context, name, *metadata_args, &block)
158+
end
155159

156-
if valid_name?(name)
157-
warn_if_key_taken context, name, block
158-
shared_example_groups[context][name] = shared_module
159-
else
160-
metadata_args.unshift name
160+
unless valid_name?(name)
161+
raise ArgumentError, "Shared example group names can only be a string, " \
162+
"symbol or module but got: #{name.inspect}"
161163
end
162164

163-
return if metadata_args.empty?
164-
RSpec.configuration.include shared_module, *metadata_args
165+
ensure_block_has_source_location(block) { CallerFilter.first_non_rspec_line }
166+
warn_if_key_taken context, name, block
167+
168+
metadata = Metadata.build_hash_from(metadata_args)
169+
shared_module = SharedExampleGroupModule.new(name, block, metadata)
170+
shared_example_groups[context][name] = shared_module
165171
end
166172

167173
def find(lookup_contexts, name)
@@ -175,6 +181,25 @@ def find(lookup_contexts, name)
175181

176182
private
177183

184+
# TODO: remove this in RSpec 4. This exists only to support
185+
# `config.shared_context_metadata_behavior == :trigger_inclusion`,
186+
# the legacy behavior of shared context metadata, which we do
187+
# not want to support in RSpec 4.
188+
def legacy_add(context, name, *metadata_args, &block)
189+
ensure_block_has_source_location(block) { CallerFilter.first_non_rspec_line }
190+
shared_module = SharedExampleGroupModule.new(name, block, {})
191+
192+
if valid_name?(name)
193+
warn_if_key_taken context, name, block
194+
shared_example_groups[context][name] = shared_module
195+
else
196+
metadata_args.unshift name
197+
end
198+
199+
return if metadata_args.empty?
200+
RSpec.configuration.include shared_module, *metadata_args
201+
end
202+
178203
def shared_example_groups
179204
@shared_example_groups ||= Hash.new { |hash, context| hash[context] = {} }
180205
end

spec/rspec/core/configuration_spec.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2414,6 +2414,31 @@ def emulate_not_configured_expectation_framework
24142414
end
24152415
end
24162416

2417+
describe "#shared_context_metadata_behavior" do
2418+
it "defaults to :trigger_inclusion for backwards compatibility" do
2419+
expect(config.shared_context_metadata_behavior).to eq :trigger_inclusion
2420+
end
2421+
2422+
it "can be set to :apply_to_host_groups" do
2423+
config.shared_context_metadata_behavior = :apply_to_host_groups
2424+
expect(config.shared_context_metadata_behavior).to eq :apply_to_host_groups
2425+
end
2426+
2427+
it "can be set to :trigger_inclusion explicitly" do
2428+
config.shared_context_metadata_behavior = :trigger_inclusion
2429+
expect(config.shared_context_metadata_behavior).to eq :trigger_inclusion
2430+
end
2431+
2432+
it "cannot be set to any other values" do
2433+
expect {
2434+
config.shared_context_metadata_behavior = :another_value
2435+
}.to raise_error(ArgumentError, a_string_including(
2436+
"shared_context_metadata_behavior",
2437+
":another_value", ":trigger_inclusion", ":apply_to_host_groups"
2438+
))
2439+
end
2440+
end
2441+
24172442
# assigns files_or_directories_to_run and triggers post-processing
24182443
# via `files_to_run`.
24192444
def assign_files_or_directories_to_run(*value)

0 commit comments

Comments
 (0)