Skip to content

Commit fcbb360

Browse files
authored
Merge pull request #2066 from rubocop/include-examples
Add new `RSpec/IncludeExamples` cop to prefer `it_behaves_like` over `include_examples`
2 parents 4107035 + 8d05f42 commit fcbb360

17 files changed

+169
-46
lines changed

.rubocop.yml

+4
Original file line numberDiff line numberDiff line change
@@ -289,3 +289,7 @@ Performance/StringIdentifierArgument: {Enabled: true}
289289
Performance/StringInclude: {Enabled: true}
290290
Performance/Sum: {Enabled: true}
291291
Performance/ZipWithoutBlock: {Enabled: true}
292+
293+
# Enable our own pending cops.
294+
295+
RSpec/IncludeExamples: {Enabled: true}

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
- Fix false positive in `RSpec/Pending`, where it would mark the default block `it` as an offense. ([@bquorning])
66
- Fix issue when `Style/ContextWording` is configured with a Prefix being interpreted as a boolean, like `on`. ([@sakuro])
7+
- Add new `RSpec/IncludeExamples` cop to enforce using `it_behaves_like` over `include_examples`. ([@dvandersluis])
78

89
## 3.5.0 (2025-02-16)
910

config/default.yml

+6
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,12 @@ RSpec/ImplicitSubject:
532532
VersionChanged: '2.13'
533533
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ImplicitSubject
534534

535+
RSpec/IncludeExamples:
536+
Description: Checks for usage of `include_examples`.
537+
Enabled: pending
538+
VersionAdded: "<<next>>"
539+
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/IncludeExamples
540+
535541
RSpec/IndexedLet:
536542
Description: Do not set up test data using indexes (e.g., `item_1`, `item_2`).
537543
Enabled: true

docs/modules/ROOT/pages/cops.adoc

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
* xref:cops_rspec.adoc#rspecimplicitblockexpectation[RSpec/ImplicitBlockExpectation]
5151
* xref:cops_rspec.adoc#rspecimplicitexpect[RSpec/ImplicitExpect]
5252
* xref:cops_rspec.adoc#rspecimplicitsubject[RSpec/ImplicitSubject]
53+
* xref:cops_rspec.adoc#rspecincludeexamples[RSpec/IncludeExamples]
5354
* xref:cops_rspec.adoc#rspecindexedlet[RSpec/IndexedLet]
5455
* xref:cops_rspec.adoc#rspecinstancespy[RSpec/InstanceSpy]
5556
* xref:cops_rspec.adoc#rspecinstancevariable[RSpec/InstanceVariable]

docs/modules/ROOT/pages/cops_rspec.adoc

+39
Original file line numberDiff line numberDiff line change
@@ -2743,6 +2743,45 @@ it { expect(named_subject).to be_truthy }
27432743
27442744
* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ImplicitSubject
27452745
2746+
[#rspecincludeexamples]
2747+
== RSpec/IncludeExamples
2748+
2749+
|===
2750+
| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed
2751+
2752+
| Pending
2753+
| Yes
2754+
| Always
2755+
| <<next>>
2756+
| -
2757+
|===
2758+
2759+
Checks for usage of `include_examples`.
2760+
2761+
`include_examples`, unlike `it_behaves_like`, does not create its
2762+
own context. As such, using `subject`, `let`, `before`/`after`, etc.
2763+
within shared examples included with `include_examples` can have
2764+
unexpected behavior and side effects.
2765+
2766+
Prefer using `it_behaves_like` instead.
2767+
2768+
[#examples-rspecincludeexamples]
2769+
=== Examples
2770+
2771+
[source,ruby]
2772+
----
2773+
# bad
2774+
include_examples 'examples'
2775+
2776+
# good
2777+
it_behaves_like 'examples'
2778+
----
2779+
2780+
[#references-rspecincludeexamples]
2781+
=== References
2782+
2783+
* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/IncludeExamples
2784+
27462785
[#rspecindexedlet]
27472786
== RSpec/IndexedLet
27482787
+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
module RSpec
6+
# Checks for usage of `include_examples`.
7+
#
8+
# `include_examples`, unlike `it_behaves_like`, does not create its
9+
# own context. As such, using `subject`, `let`, `before`/`after`, etc.
10+
# within shared examples included with `include_examples` can have
11+
# unexpected behavior and side effects.
12+
#
13+
# Prefer using `it_behaves_like` instead.
14+
#
15+
# @example
16+
# # bad
17+
# include_examples 'examples'
18+
#
19+
# # good
20+
# it_behaves_like 'examples'
21+
#
22+
class IncludeExamples < Base
23+
extend AutoCorrector
24+
25+
MSG = 'Prefer `it_behaves_like` over `include_examples`.'
26+
27+
RESTRICT_ON_SEND = %i[include_examples].freeze
28+
29+
def on_send(node)
30+
selector = node.loc.selector
31+
32+
add_offense(selector) do |corrector|
33+
corrector.replace(selector, 'it_behaves_like')
34+
end
35+
end
36+
end
37+
end
38+
end
39+
end

lib/rubocop/cop/rspec_cops.rb

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
require_relative 'rspec/implicit_block_expectation'
4949
require_relative 'rspec/implicit_expect'
5050
require_relative 'rspec/implicit_subject'
51+
require_relative 'rspec/include_examples'
5152
require_relative 'rspec/indexed_let'
5253
require_relative 'rspec/instance_spy'
5354
require_relative 'rspec/instance_variable'

spec/rubocop/cop/rspec/base_spec.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
113113
RUBY
114114
end
115115

116-
include_examples 'it detects `describe`'
116+
it_behaves_like 'it detects `describe`'
117117
end
118118

119119
context 'when `epic` is set as an alias to example group' do
@@ -133,7 +133,7 @@ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
133133
RUBY
134134
end
135135

136-
include_examples 'it detects `describe`'
136+
it_behaves_like 'it detects `describe`'
137137
end
138138
end
139139
end

spec/rubocop/cop/rspec/empty_line_after_hook_spec.rb

+4-4
Original file line numberDiff line numberDiff line change
@@ -310,8 +310,8 @@
310310
end
311311

312312
context 'when AllowConsecutiveOneLiners option has default value `true`' do
313-
include_examples 'always require empty line after hook groups'
314-
include_examples 'never allows consecutive multiline blocks'
313+
it_behaves_like 'always require empty line after hook groups'
314+
it_behaves_like 'never allows consecutive multiline blocks'
315315

316316
it 'allows multiple one-liner blocks' do
317317
expect_offense(<<~RUBY)
@@ -381,8 +381,8 @@
381381
context 'when AllowConsecutiveOneLiners option `false`' do
382382
let(:cop_config) { { 'AllowConsecutiveOneLiners' => false } }
383383

384-
include_examples 'always require empty line after hook groups'
385-
include_examples 'never allows consecutive multiline blocks'
384+
it_behaves_like 'always require empty line after hook groups'
385+
it_behaves_like 'never allows consecutive multiline blocks'
386386

387387
it 'registers an offense for multiple one-liner same hook blocks' do
388388
expect_offense(<<~RUBY)

spec/rubocop/cop/rspec/hook_argument_spec.rb

+7-7
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@
2929
end
3030

3131
shared_examples 'an example hook' do
32-
include_examples 'ignored hooks'
33-
include_examples 'detects style', 'before(:each) { foo }', 'each'
34-
include_examples 'detects style', 'before(:example) { foo }', 'example'
35-
include_examples 'detects style', 'before { foo }', 'implicit'
32+
it_behaves_like 'ignored hooks'
33+
it_behaves_like 'detects style', 'before(:each) { foo }', 'each'
34+
it_behaves_like 'detects style', 'before(:example) { foo }', 'example'
35+
it_behaves_like 'detects style', 'before { foo }', 'implicit'
3636
end
3737

3838
context 'when EnforcedStyle is :implicit' do
@@ -87,7 +87,7 @@
8787
RUBY
8888
end
8989

90-
include_examples 'an example hook'
90+
it_behaves_like 'an example hook'
9191

9292
context 'when Ruby 2.7', :ruby27 do
9393
it 'detects :each for hooks' do
@@ -172,7 +172,7 @@
172172
RUBY
173173
end
174174

175-
include_examples 'an example hook'
175+
it_behaves_like 'an example hook'
176176

177177
context 'when Ruby 2.7', :ruby27 do
178178
it 'does not flag :each for hooks' do
@@ -257,7 +257,7 @@
257257
RUBY
258258
end
259259

260-
include_examples 'an example hook'
260+
it_behaves_like 'an example hook'
261261

262262
context 'when Ruby 2.7', :ruby27 do
263263
it 'does not flag :example for hooks' do

spec/rubocop/cop/rspec/implicit_expect_spec.rb

+7-7
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
expect_no_offenses('it { is_expected.not_to be_truthy }')
4141
end
4242

43-
include_examples 'detects style', 'it { should be_truthy }', 'should'
43+
it_behaves_like 'detects style', 'it { should be_truthy }', 'should'
4444
end
4545

4646
context 'when EnforcedStyle is should' do
@@ -89,12 +89,12 @@
8989
expect_no_offenses('it { should_not be_truthy }')
9090
end
9191

92-
include_examples 'detects style',
93-
'it { is_expected.to be_truthy }',
94-
'is_expected'
92+
it_behaves_like 'detects style',
93+
'it { is_expected.to be_truthy }',
94+
'is_expected'
9595

96-
include_examples 'detects style',
97-
'it { should be_truthy }',
98-
'should'
96+
it_behaves_like 'detects style',
97+
'it { should be_truthy }',
98+
'should'
9999
end
100100
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe RuboCop::Cop::RSpec::IncludeExamples, :config do
4+
it 'registers an offense and corrects for `include_examples`' do
5+
expect_offense(<<~RUBY)
6+
include_examples 'examples'
7+
^^^^^^^^^^^^^^^^ Prefer `it_behaves_like` over `include_examples`.
8+
RUBY
9+
10+
expect_correction(<<~RUBY)
11+
it_behaves_like 'examples'
12+
RUBY
13+
end
14+
15+
it 'does not register an offense for `it_behaves_like`' do
16+
expect_no_offenses(<<~RUBY)
17+
it_behaves_like 'examples'
18+
RUBY
19+
end
20+
21+
it 'does not register an offense for `it_should_behave_like`' do
22+
expect_no_offenses(<<~RUBY)
23+
it_should_behave_like 'examples'
24+
RUBY
25+
end
26+
27+
it 'does not register an offense for `include_context`' do
28+
expect_no_offenses(<<~RUBY)
29+
include_context 'context'
30+
RUBY
31+
end
32+
end

spec/rubocop/cop/rspec/message_expectation_spec.rb

+4-4
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
expect_no_offenses('allow(foo).to receive(:bar)')
1818
end
1919

20-
include_examples 'detects style', 'allow(foo).to receive(:bar)', 'allow'
21-
include_examples 'detects style', 'expect(foo).to receive(:bar)', 'expect'
20+
it_behaves_like 'detects style', 'allow(foo).to receive(:bar)', 'allow'
21+
it_behaves_like 'detects style', 'expect(foo).to receive(:bar)', 'expect'
2222
end
2323

2424
context 'when EnforcedStyle is expect' do
@@ -37,7 +37,7 @@
3737
expect_no_offenses('expect(foo).to receive(:bar)')
3838
end
3939

40-
include_examples 'detects style', 'expect(foo).to receive(:bar)', 'expect'
41-
include_examples 'detects style', 'allow(foo).to receive(:bar)', 'allow'
40+
it_behaves_like 'detects style', 'expect(foo).to receive(:bar)', 'expect'
41+
it_behaves_like 'detects style', 'allow(foo).to receive(:bar)', 'allow'
4242
end
4343
end

spec/rubocop/cop/rspec/message_spies_spec.rb

+8-8
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,11 @@
6767
expect_no_offenses('expect(foo).to have_received(:bar)')
6868
end
6969

70-
include_examples 'detects style', 'expect(foo).to receive(:bar)', 'receive'
70+
it_behaves_like 'detects style', 'expect(foo).to receive(:bar)', 'receive'
7171

72-
include_examples 'detects style',
73-
'expect(foo).to have_received(:bar)',
74-
'have_received'
72+
it_behaves_like 'detects style',
73+
'expect(foo).to have_received(:bar)',
74+
'have_received'
7575
end
7676

7777
context 'when EnforcedStyle is receive' do
@@ -140,10 +140,10 @@
140140
expect_no_offenses('expect(foo).to receive(:bar)')
141141
end
142142

143-
include_examples 'detects style', 'expect(foo).to receive(:bar)', 'receive'
143+
it_behaves_like 'detects style', 'expect(foo).to receive(:bar)', 'receive'
144144

145-
include_examples 'detects style',
146-
'expect(foo).to have_received(:bar)',
147-
'have_received'
145+
it_behaves_like 'detects style',
146+
'expect(foo).to have_received(:bar)',
147+
'have_received'
148148
end
149149
end

spec/rubocop/cop/rspec/predicate_matcher_spec.rb

+4-4
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@
186186
context 'when strict is true' do
187187
let(:strict) { true }
188188

189-
include_examples 'inflected common'
189+
it_behaves_like 'inflected common'
190190

191191
it 'accepts strict checking boolean matcher' do
192192
expect_no_offenses(<<~RUBY)
@@ -203,7 +203,7 @@
203203
context 'when strict is false' do
204204
let(:strict) { false }
205205

206-
include_examples 'inflected common'
206+
it_behaves_like 'inflected common'
207207

208208
it 'registers an offense for a predicate method in actual' do
209209
expect_offense(<<~RUBY)
@@ -489,13 +489,13 @@
489489
context 'when strict is true' do
490490
let(:strict) { true }
491491

492-
include_examples 'explicit', 'be(true)', 'be(false)'
492+
it_behaves_like 'explicit', 'be(true)', 'be(false)'
493493
end
494494

495495
context 'when strict is false' do
496496
let(:strict) { false }
497497

498-
include_examples 'explicit', 'be_truthy', 'be_falsey'
498+
it_behaves_like 'explicit', 'be_truthy', 'be_falsey'
499499
end
500500
end
501501
end

spec/rubocop/cop/rspec/subject_declaration_spec.rb

+4-4
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,10 @@
5151
end
5252
end
5353

54-
include_examples 'flag unclear subject declaration', :subject
55-
include_examples 'flag unclear subject declaration', 'subject'
56-
include_examples 'flag unclear subject declaration', :subject!
57-
include_examples 'flag unclear subject declaration', 'subject!'
54+
it_behaves_like 'flag unclear subject declaration', :subject
55+
it_behaves_like 'flag unclear subject declaration', 'subject'
56+
it_behaves_like 'flag unclear subject declaration', :subject!
57+
it_behaves_like 'flag unclear subject declaration', 'subject!'
5858

5959
context 'when subject helper is used directly' do
6060
it 'does not register an offense on named subject' do

spec/rubocop/rspec/hook_spec.rb

+6-6
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,14 @@ def hook(source)
6464
end
6565
end
6666

67-
include_examples 'standardizes scope', 'before(:each) { }', :each
68-
include_examples 'standardizes scope', 'around(:example) { }', :each
69-
include_examples 'standardizes scope', 'after { }', :each
67+
it_behaves_like 'standardizes scope', 'before(:each) { }', :each
68+
it_behaves_like 'standardizes scope', 'around(:example) { }', :each
69+
it_behaves_like 'standardizes scope', 'after { }', :each
7070

71-
include_examples 'standardizes scope', 'before(:all) { }', :context
72-
include_examples 'standardizes scope', 'around(:context) { }', :context
71+
it_behaves_like 'standardizes scope', 'before(:all) { }', :context
72+
it_behaves_like 'standardizes scope', 'around(:context) { }', :context
7373

74-
include_examples 'standardizes scope', 'after(:suite) { }', :suite
74+
it_behaves_like 'standardizes scope', 'after(:suite) { }', :suite
7575
end
7676

7777
describe '#metadata' do

0 commit comments

Comments
 (0)