Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 79072ad

Browse files
author
David Worth
committed
Add a cop to enforce expected exceptions to be specified
In order to avoid false-positives in the context of exception checking it is valuable to be able to enforce that each `to raise` pair to include specifying information about the type of exception.
1 parent a791b4d commit 79072ad

File tree

7 files changed

+288
-0
lines changed

7 files changed

+288
-0
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* Fix `FactoryBot/AttributeDefinedStatically` not working when there is a non-symbol key. ([@vzvu3k6k][])
77
* Fix false positive in `RSpec/ImplicitSubject` when `is_expected` is used inside `its()` block. ([@Darhazer][])
88
* Add `single_statement_only` style to `RSpec/ImplicitSubject` as a more relaxed alternative to `single_line_only`. ([@Darhazer][])
9+
* Add `RSpec/UnspecifiedException` as a non-default cop to encourage more-specific `expect{}.to raise_error(ExceptionType)`, or `raise_exception` style handling of exceptions. ([@daveworth][])
910

1011
## 1.29.1 (2018-09-01)
1112

@@ -381,3 +382,4 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
381382
[@seanpdoyle]: https://github.com/seanpdoyle
382383
[@vzvu3k6k]: https://github.com/vzvu3k6k
383384
[@BrentWheeldon]: https://github.com/BrentWheeldon
385+
[@daveworth]: https://github.com/daveworth

config/default.yml

+5
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,11 @@ RSpec/SubjectStub:
399399
Enabled: true
400400
StyleGuide: http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SubjectStub
401401

402+
RSpec/UnspecifiedException:
403+
Description: Checks for a specified error in checking raised errors.
404+
Enabled: true
405+
StyleGuide: http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/UnspecifiedException
406+
402407
RSpec/VerifiedDoubles:
403408
Description: Prefer using verifying doubles over normal doubles.
404409
Enabled: true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
module RuboCop
2+
module Cop
3+
module RSpec
4+
# Checks for a specified error in checking raised errors.
5+
#
6+
# Enforces one of an Exception type, a string, or a regular
7+
# expression to match against the exception message as a parameter
8+
# to `raise_error`
9+
#
10+
# @example
11+
#
12+
# # bad
13+
# expect {
14+
# raise StandardError.new('error')
15+
# }.to raise_error
16+
#
17+
# # good
18+
# expect {
19+
# raise StandardError.new('error')
20+
# }.to raise_error(StandardError)
21+
#
22+
# expect {
23+
# raise StandardError.new('error')
24+
# }.to raise_error('error')
25+
#
26+
# expect {
27+
# raise StandardError.new('error')
28+
# }.to raise_error(/err/)
29+
#
30+
# expect { do_something }.not_to raise_error
31+
class UnspecifiedException < Cop
32+
MSG = 'Specify the exception being captured'.freeze
33+
34+
def_node_matcher :empty_raise_error_or_exception, <<-PATTERN.freeze
35+
(send
36+
(block
37+
(send nil? :expect) ...)
38+
:to
39+
(send nil? {:raise_error :raise_exception})
40+
)
41+
PATTERN
42+
43+
def on_send(node)
44+
return unless empty_exception_matcher?(node)
45+
46+
add_offense(
47+
node.children.last,
48+
location: :expression
49+
)
50+
end
51+
52+
def empty_exception_matcher?(node)
53+
empty_raise_error_or_exception(node) && !block_with_args?(node.parent)
54+
end
55+
56+
def block_with_args?(node)
57+
return unless node && node.block_type?
58+
59+
node.arguments?
60+
end
61+
end
62+
end
63+
end
64+
end

lib/rubocop/cop/rspec_cops.rb

+1
Original file line numberDiff line numberDiff line change
@@ -72,5 +72,6 @@
7272
require_relative 'rspec/shared_examples'
7373
require_relative 'rspec/single_argument_message_chain'
7474
require_relative 'rspec/subject_stub'
75+
require_relative 'rspec/unspecified_exception'
7576
require_relative 'rspec/verified_doubles'
7677
require_relative 'rspec/void_expect'

manual/cops.md

+1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
* [RSpec/SharedExamples](cops_rspec.md#rspecsharedexamples)
7474
* [RSpec/SingleArgumentMessageChain](cops_rspec.md#rspecsingleargumentmessagechain)
7575
* [RSpec/SubjectStub](cops_rspec.md#rspecsubjectstub)
76+
* [RSpec/UnspecifiedException](cops_rspec.md#rspecunspecifiedexception)
7677
* [RSpec/VerifiedDoubles](cops_rspec.md#rspecverifieddoubles)
7778
* [RSpec/VoidExpect](cops_rspec.md#rspecvoidexpect)
7879

manual/cops_rspec.md

+40
Original file line numberDiff line numberDiff line change
@@ -2463,6 +2463,46 @@ end
24632463

24642464
* [http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SubjectStub](http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SubjectStub)
24652465

2466+
## RSpec/UnspecifiedException
2467+
2468+
Enabled by default | Supports autocorrection
2469+
--- | ---
2470+
Enabled | No
2471+
2472+
Checks for a specified error in checking raised errors.
2473+
2474+
Enforces one of an Exception type, a string, or a regular
2475+
expression to match against the exception message as a parameter
2476+
to `raise_error`
2477+
2478+
### Examples
2479+
2480+
```ruby
2481+
# bad
2482+
expect {
2483+
raise StandardError.new('error')
2484+
}.to raise_error
2485+
2486+
# good
2487+
expect {
2488+
raise StandardError.new('error')
2489+
}.to raise_error(StandardError)
2490+
2491+
expect {
2492+
raise StandardError.new('error')
2493+
}.to raise_error('error')
2494+
2495+
expect {
2496+
raise StandardError.new('error')
2497+
}.to raise_error(/err/)
2498+
2499+
expect { do_something }.not_to raise_error
2500+
```
2501+
2502+
### References
2503+
2504+
* [http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/UnspecifiedException](http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/UnspecifiedException)
2505+
24662506
## RSpec/VerifiedDoubles
24672507

24682508
Enabled by default | Supports autocorrection
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
RSpec.describe RuboCop::Cop::RSpec::UnspecifiedException do
2+
subject(:cop) { described_class.new }
3+
4+
context 'with raise_error matcher' do
5+
it 'detects the `unspecified_exception` offense' do
6+
expect_offense(<<-RUBY)
7+
expect {
8+
raise StandardError
9+
}.to raise_error
10+
^^^^^^^^^^^ Specify the exception being captured
11+
RUBY
12+
end
13+
14+
it 'allows empty exception specification when not expecting an error' do
15+
expect_no_offenses(<<-RUBY)
16+
expect {
17+
raise StandardError
18+
}.not_to raise_error
19+
RUBY
20+
end
21+
22+
it 'allows exception classes' do
23+
expect_no_offenses(<<-RUBY)
24+
expect {
25+
raise StandardError
26+
}.to raise_error(StandardError)
27+
RUBY
28+
end
29+
30+
it 'allows exception messages' do
31+
expect_no_offenses(<<-RUBY)
32+
expect {
33+
raise StandardError.new('error')
34+
}.to raise_error('error')
35+
RUBY
36+
end
37+
38+
it 'allows exception types with messages' do
39+
expect_no_offenses(<<-RUBY)
40+
expect {
41+
raise StandardError.new('error')
42+
}.to raise_error(StandardError, 'error')
43+
RUBY
44+
end
45+
46+
it 'allows exception matching regular expressions' do
47+
expect_no_offenses(<<-RUBY)
48+
expect {
49+
raise StandardError.new('error')
50+
}.to raise_error(/err/)
51+
RUBY
52+
end
53+
54+
it 'allows exception types with matching regular expressions' do
55+
expect_no_offenses(<<-RUBY)
56+
expect {
57+
raise StandardError.new('error')
58+
}.to raise_error(StandardError, /err/)
59+
RUBY
60+
end
61+
62+
it 'allows classes with blocks with braces' do
63+
expect_no_offenses(<<-RUBY)
64+
expect {
65+
raise StandardError.new('error')
66+
}.to raise_error { |err| err.data }
67+
RUBY
68+
end
69+
70+
it 'allows classes with blocks with do/end' do
71+
expect_no_offenses(<<-RUBY)
72+
expect {
73+
raise StandardError.new('error')
74+
}.to raise_error do |error|
75+
error.data
76+
end
77+
RUBY
78+
end
79+
80+
it 'allows parameterized exceptions' do
81+
expect_no_offenses(<<-RUBY)
82+
my_exception = StandardError.new('my exception')
83+
expect {
84+
raise my_exception
85+
}.to raise_error(my_exception)
86+
RUBY
87+
end
88+
end
89+
90+
context 'with raise_exception matcher' do
91+
it 'detects the `unspecified_exception` offense' do
92+
expect_offense(<<-RUBY)
93+
expect {
94+
raise StandardError
95+
}.to raise_exception
96+
^^^^^^^^^^^^^^^ Specify the exception being captured
97+
RUBY
98+
end
99+
100+
it 'allows empty exception specification when not expecting an error' do
101+
expect_no_offenses(<<-RUBY)
102+
expect {
103+
raise StandardError
104+
}.not_to raise_exception
105+
RUBY
106+
end
107+
108+
it 'allows exception classes' do
109+
expect_no_offenses(<<-RUBY)
110+
expect {
111+
raise StandardError
112+
}.to raise_exception(StandardError)
113+
RUBY
114+
end
115+
116+
it 'allows exception messages' do
117+
expect_no_offenses(<<-RUBY)
118+
expect {
119+
raise StandardError.new('error')
120+
}.to raise_exception('error')
121+
RUBY
122+
end
123+
124+
it 'allows exception types with messages' do
125+
expect_no_offenses(<<-RUBY)
126+
expect {
127+
raise StandardError.new('error')
128+
}.to raise_exception(StandardError, 'error')
129+
RUBY
130+
end
131+
132+
it 'allows exception matching regular expressions' do
133+
expect_no_offenses(<<-RUBY)
134+
expect {
135+
raise StandardError.new('error')
136+
}.to raise_exception(/err/)
137+
RUBY
138+
end
139+
140+
it 'allows exception types with matching regular expressions' do
141+
expect_no_offenses(<<-RUBY)
142+
expect {
143+
raise StandardError.new('error')
144+
}.to raise_exception(StandardError, /err/)
145+
RUBY
146+
end
147+
148+
it 'allows classes with blocks with braces' do
149+
expect_no_offenses(<<-RUBY)
150+
expect {
151+
raise StandardError.new('error')
152+
}.to raise_exception { |err| err.data }
153+
RUBY
154+
end
155+
156+
it 'allows classes with blocks with do/end' do
157+
expect_no_offenses(<<-RUBY)
158+
expect {
159+
raise StandardError.new('error')
160+
}.to raise_exception do |error|
161+
error.data
162+
end
163+
RUBY
164+
end
165+
166+
it 'allows parameterized exceptions' do
167+
expect_no_offenses(<<-RUBY)
168+
my_exception = StandardError.new('my exception')
169+
expect {
170+
raise my_exception
171+
}.to raise_exception(my_exception)
172+
RUBY
173+
end
174+
end
175+
end

0 commit comments

Comments
 (0)