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

Commit a34d983

Browse files
committed
Merge pull request #1479 from rspec/fix-around-hook-yielded-arg
Fix around hook yielded arg
2 parents 4984a2b + ec03ab2 commit a34d983

File tree

3 files changed

+85
-25
lines changed

3 files changed

+85
-25
lines changed

Changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ Bug Fixes:
115115
that it applies to files loaded by `--require`. (Myron Marston)
116116
* Issue a warning when you set `config.deprecation_stream` too late for
117117
it to take effect because the reporter has already been setup. (Myron Marston)
118+
* Add the full `RSpec::Core::Example` interface to the argument yielded
119+
to `around` hooks. (Myron Marston)
118120

119121
### 3.0.0.beta2 / 2014-02-17
120122
[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.0.0.beta1...v3.0.0.beta2)

lib/rspec/core/example.rb

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
module RSpec
22
module Core
33
# Wrapper for an instance of a subclass of {ExampleGroup}. An instance of
4-
# `RSpec::Core::Example` is returned by the {ExampleGroup#example example}
5-
# method exposed to examples, {Hooks#before before} and {Hooks#after after}
6-
# hooks, and yielded to {Hooks#around around} hooks.
4+
# `RSpec::Core::Example` is returned by example definition methods
5+
# such as {ExampleGroup.it it} and is yielded to the {ExampleGroup.it it},
6+
# {Hooks#before before}, {Hooks#after after}, {Hooks#around around},
7+
# {MemoizedHelpers::ClassMethods#let let} and
8+
# {MemoizedHelpers::ClassMethods#subject subject} blocks.
79
#
810
# This allows us to provide rich metadata about each individual
911
# example without adding tons of methods directly to the ExampleGroup
@@ -15,17 +17,17 @@ module Core
1517
# @example
1618
#
1719
# RSpec.configure do |config|
18-
# config.before do
20+
# config.before do |example|
1921
# log example.description
2022
# end
2123
#
22-
# config.after do
24+
# config.after do |example|
2325
# log example.description
2426
# end
2527
#
26-
# config.around do |ex|
28+
# config.around do |example|
2729
# log example.description
28-
# ex.run
30+
# example.run
2931
# end
3032
# end
3133
#
@@ -38,17 +40,32 @@ module Core
3840
#
3941
# @see ExampleGroup
4042
# @note Example blocks are evaluated in the context of an instance
41-
# of an `ExampleGroup`, not in the context of an instance of `Example`.
43+
# of an `ExampleGroup`, not in the context of an instance of `Example`.
4244
class Example
4345
# @private
4446
#
4547
# Used to define methods that delegate to this example's metadata
46-
def self.delegate_to_metadata(*keys)
47-
keys.each { |key| define_method(key) { @metadata[key] } }
48+
def self.delegate_to_metadata(key)
49+
define_method(key) { @metadata[key] }
4850
end
4951

50-
delegate_to_metadata :execution_result, :file_path, :full_description,
51-
:location, :pending, :skip
52+
# @return [ExecutionResult] represents the result of running this example.
53+
delegate_to_metadata :execution_result
54+
# @return [String] the relative path to the file where this example was defined.
55+
delegate_to_metadata :file_path
56+
# @return [String] the full description (including the docstrings of
57+
# all parent example groups).
58+
delegate_to_metadata :full_description
59+
# @return [String] the exact source location of this example in a form
60+
# like `./path/to/spec.rb:17`
61+
delegate_to_metadata :location
62+
# @return [Boolean] flag that indicates that the example is not expected to pass.
63+
# It will be run and will either have a pending result (if a failure occurs)
64+
# or a failed result (if no failure occurs).
65+
delegate_to_metadata :pending
66+
# @return [Boolean] flag that will cause the example to not run.
67+
# The {ExecutionResult} status will be `:pending`.
68+
delegate_to_metadata :skip
5269

5370
# Returns the string submitted to `example` or its aliases (e.g.
5471
# `specify`, `it`, etc). If no string is submitted (e.g. `it { is_expected.to
@@ -161,12 +178,10 @@ def run(example_group_instance, reporter)
161178
RSpec.current_example = nil
162179
end
163180

164-
# Wraps a `Proc` and exposes a `run` method for use in {Hooks#around
165-
# around} hooks.
166-
#
167-
# @note Procsy, itself, is not a public API, but we're documenting it
168-
# here to document how to interact with the object yielded to an
169-
# `around` hook.
181+
# Wraps both a `Proc` and an {Example} for use in {Hooks#around
182+
# around} hooks. In around hooks we need to yield this special
183+
# kind of object (rather than the raw {Example}) because when
184+
# there are multiple `around` hooks we have to wrap them recursively.
170185
#
171186
# @example
172187
#
@@ -178,23 +193,30 @@ def run(example_group_instance, reporter)
178193
# ex.run # run delegates to ex.call
179194
# end
180195
# end
196+
#
197+
# @note This class also exposes the instance methods of {Example},
198+
# proxying them through to the wrapped {Example} instance.
181199
class Procsy
182-
# The `metadata` of the {Example} instance.
183-
attr_reader :metadata
200+
# The {Example} instance.
201+
attr_reader :example
202+
203+
Example.public_instance_methods(false).each do |name|
204+
define_method(name) { |*a, &b| @example.__send__(name, *a, &b) }
205+
end
184206

185207
Proc.public_instance_methods(false).each do |name|
186208
define_method(name) { |*a, &b| @proc.__send__(name, *a, &b) }
187209
end
188210
alias run call
189211

190-
def initialize(metadata, &block)
191-
@metadata = metadata
192-
@proc = block
212+
def initialize(example, &block)
213+
@example = example
214+
@proc = block
193215
end
194216

195217
# @private
196218
def wrap(&block)
197-
self.class.new(metadata, &block)
219+
self.class.new(example, &block)
198220
end
199221
end
200222

@@ -276,7 +298,7 @@ def with_around_example_hooks(&block)
276298
if around_example_hooks.empty?
277299
yield
278300
else
279-
@example_group_class.hooks.run(:around, :example, self, Procsy.new(metadata, &block))
301+
@example_group_class.hooks.run(:around, :example, self, Procsy.new(self, &block))
280302
end
281303
rescue Exception => e
282304
set_exception(e, "in an `around(:example)` hook")

spec/rspec/core/hooks_spec.rb

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,42 @@ def parent_groups
145145
group.run
146146
expect(foos).to eq({:first => :bar, :second => :bar})
147147
end
148+
149+
it "exposes the full example interface to each around hook" do
150+
data_1 = {}
151+
data_2 = {}
152+
ex = nil
153+
154+
group = ExampleGroup.describe do
155+
def self.data_from(ex)
156+
{
157+
:description => ex.description,
158+
:full_description => ex.full_description,
159+
:example_group => ex.example_group,
160+
:file_path => ex.file_path,
161+
:location => ex.location
162+
}
163+
end
164+
165+
around do |example|
166+
data_1.update(self.class.data_from example)
167+
example.run
168+
end
169+
170+
around do |example|
171+
data_2.update(self.class.data_from example)
172+
example.run
173+
end
174+
175+
ex = example("the example") { }
176+
end
177+
178+
group.run
179+
180+
expected_data = group.data_from(ex)
181+
expect(data_1).to eq(expected_data)
182+
expect(data_2).to eq(expected_data)
183+
end
148184
end
149185

150186
context "when running the example within a block passed to a method" do

0 commit comments

Comments
 (0)