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

Commit 5ef3a63

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 6f856b7 commit 5ef3a63

File tree

6 files changed

+252
-34
lines changed

6 files changed

+252
-34
lines changed

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_group`
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_group
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: 54 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_group`: 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_group` 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_group] the configured behavior
368+
# @overload shared_context_metadata_behavior=(value)
369+
# @param value [:trigger_inclusion, :apply_to_host_group] 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_group
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_group` 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

@@ -398,6 +451,7 @@ def initialize
398451
@threadsafe = true
399452
@max_displayed_failure_line_count = 10
400453
@world = World::Null
454+
@shared_context_metadata_behavior = :trigger_inclusion
401455

402456
define_built_in_hooks
403457
end

lib/rspec/core/shared_example_group.rb

Lines changed: 37 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,10 @@ def included(klass)
2930

3031
# @private
3132
def include_in(klass, inclusion_line, args, customization_block)
33+
unless @metadata.empty?
34+
klass.update_inherited_metadata(@metadata)
35+
end
36+
3237
SharedExampleGroupInclusionStackFrame.with_frame(@description, inclusion_line) do
3338
klass.class_exec(*args, &@definition)
3439
klass.class_exec(&customization_block) if customization_block
@@ -150,18 +155,21 @@ def self.remove_globally!
150155
# @private
151156
class Registry
152157
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)
158+
if RSpec.configuration.shared_context_metadata_behavior == :trigger_inclusion
159+
return legacy_add(context, name, *metadata_args, &block)
160+
end
155161

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
162+
unless valid_name?(name)
163+
raise ArgumentError, "Shared example group names can only be a string, " \
164+
"symbol or module but got: #{name.inspect}"
161165
end
162166

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

167175
def find(lookup_contexts, name)
@@ -175,6 +183,25 @@ def find(lookup_contexts, name)
175183

176184
private
177185

186+
# TODO: remove this in RSpec 4. This exists only to support
187+
# `config.shared_context_metadata_behavior == :trigger_inclusion`,
188+
# the legacy behavior of shared context metadata, which we do
189+
# not want to support in RSpec 4.
190+
def legacy_add(context, name, *metadata_args, &block)
191+
ensure_block_has_source_location(block) { CallerFilter.first_non_rspec_line }
192+
shared_module = SharedExampleGroupModule.new(name, block, {})
193+
194+
if valid_name?(name)
195+
warn_if_key_taken context, name, block
196+
shared_example_groups[context][name] = shared_module
197+
else
198+
metadata_args.unshift name
199+
end
200+
201+
return if metadata_args.empty?
202+
RSpec.configuration.include shared_module, *metadata_args
203+
end
204+
178205
def shared_example_groups
179206
@shared_example_groups ||= Hash.new { |hash, context| hash[context] = {} }
180207
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_group" do
2423+
config.shared_context_metadata_behavior = :apply_to_host_group
2424+
expect(config.shared_context_metadata_behavior).to eq :apply_to_host_group
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_group"
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)