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

Commit e072e53

Browse files
committed
Warn users when overriding methods (via let, def or define_method) in the same example group.
1 parent 1225032 commit e072e53

File tree

3 files changed

+111
-0
lines changed

3 files changed

+111
-0
lines changed

features/example_groups/shared_examples.feature

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,50 @@ Feature: shared examples
1919
anything special (like autoload). Doing so would require a strict naming
2020
convention for files that would break existing suites.
2121

22+
**WARNING:** When you include parameterized examples in the current context multiple
23+
times, you may override previous method definitions and last declaration wins.
24+
So if you have this kind of shared example (or shared context)
25+
26+
```ruby
27+
RSpec.shared_examples "some example" do |parameter|
28+
\# Same behavior is triggered also with either `def something; 'some value'; end`
29+
\# or `define_method(:something) { 'some value' }`
30+
let(:something) { parameter }
31+
it "uses the given parameter" do
32+
expect(something).to eq(parameter)
33+
end
34+
end
35+
36+
RSpec.describe SomeClass do
37+
include_example "some example", "parameter1"
38+
include_example "some example", "parameter2"
39+
end
40+
```
41+
42+
You're actually doing this (notice that first example will fail):
43+
44+
```ruby
45+
RSpec.describe SomeClass do
46+
\# Reordered code for better understanding of what is happening
47+
let(:something) { "parameter1" }
48+
let(:something) { "parameter2" }
49+
50+
it "uses the given parameter" do
51+
\# This example will fail because last let "wins"
52+
expect(something).to eq("parameter1")
53+
end
54+
55+
it "uses the given parameter" do
56+
expect(something).to eq("parameter2")
57+
end
58+
end
59+
```
60+
61+
To prevent this kind of subtle error a warning is emitted if you declare multiple
62+
methods with the same name in the same context. Should you get this warning
63+
the simplest solution is to replace `include_example` with `it_behaves_like`, in this
64+
way method overriding is avoided because of the nested context created by `it_behaves_like`
65+
2266
Conventions:
2367
------------
2468

lib/rspec/core/example_group.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
module RSpec
44
module Core
5+
# rubocop:disable Style/ClassLength
6+
57
# ExampleGroup and {Example} are the main structural elements of
68
# rspec-core. Consider this example:
79
#
@@ -650,6 +652,16 @@ def self.method_missing(name, *args)
650652
end
651653
private_class_method :method_missing
652654

655+
# @private
656+
def self.method_added(method_name)
657+
if (@__added_methods ||= Set.new).include?(method_name)
658+
RSpec.warning "`#{self}##{method_name}` is being redefined " \
659+
"at #{RSpec::CallerFilter.first_non_rspec_line}. The original " \
660+
"definition will never be used and can be removed", :call_site => nil
661+
end
662+
@__added_methods << method_name
663+
end
664+
653665
private
654666

655667
def method_missing(name, *args)
@@ -665,6 +677,8 @@ def method_missing(name, *args)
665677
end
666678
end
667679

680+
# rubocop:enable Style/ClassLength
681+
668682
# @private
669683
# Unnamed example group used by `SuiteHookContext`.
670684
class AnonymousExampleGroup < ExampleGroup

spec/rspec/core/example_group_spec.rb

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,6 +1306,59 @@ def extract_execution_results(group)
13061306

13071307
end
13081308

1309+
describe "when methods are redefined" do
1310+
1311+
context "in the same example group" do
1312+
1313+
it 'emits a warning when overriding methods using `let`' do
1314+
expect {
1315+
RSpec.describe do
1316+
let(:foo) { 'first value' }
1317+
let(:foo) { 'second value' }
1318+
end
1319+
}.to output(a_string_including("#foo", "is being redefined at #{__FILE__}:#{__LINE__ - 2}")).to_stderr
1320+
end
1321+
1322+
it 'emits a warning when overriding methods using `def`' do
1323+
expect {
1324+
RSpec.describe do
1325+
let(:foo) { 'first value' }
1326+
def foo; 'second value'; end
1327+
end
1328+
}.to output(a_string_including("#foo", "is being redefined at #{__FILE__}:#{__LINE__ - 2}")).to_stderr
1329+
end
1330+
1331+
it 'emits a warning when overriding methods using `define_method`' do
1332+
expect {
1333+
RSpec.describe do
1334+
let(:foo) { 'first value' }
1335+
define_method(:foo) { 'second value' }
1336+
end
1337+
}.to output(a_string_including("#foo", "is being redefined at #{__FILE__}:#{__LINE__ - 2}")).to_stderr
1338+
end
1339+
1340+
end
1341+
1342+
context "in nested example groups" do
1343+
1344+
it 'does not emit warnings' do
1345+
expect {
1346+
RSpec.describe do
1347+
let(:foo) { 'first value' }
1348+
context 'in sub context' do
1349+
let(:foo) { 'second value' }
1350+
context 'in sub sub context' do
1351+
def foo; 'third value'; end
1352+
end
1353+
end
1354+
end
1355+
}.to avoid_outputting.to_stdout.and avoid_outputting.to_stderr
1356+
end
1357+
1358+
end
1359+
1360+
end
1361+
13091362
describe "ivars are not shared across examples" do
13101363
it "(first example)" do
13111364
@a = 1

0 commit comments

Comments
 (0)