rubocop-rspec 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/README.md +23 -0
- data/Rakefile +19 -1
- data/config/default.yml +78 -15
- data/lib/rubocop-rspec.rb +19 -1
- data/lib/rubocop/cop/rspec/any_instance.rb +5 -1
- data/lib/rubocop/cop/rspec/be_eql.rb +58 -0
- data/lib/rubocop/cop/rspec/describe_class.rb +2 -3
- data/lib/rubocop/cop/rspec/describe_method.rb +4 -3
- data/lib/rubocop/cop/rspec/described_class.rb +4 -35
- data/lib/rubocop/cop/rspec/empty_example_group.rb +100 -0
- data/lib/rubocop/cop/rspec/example_length.rb +5 -2
- data/lib/rubocop/cop/rspec/example_wording.rb +5 -2
- data/lib/rubocop/cop/rspec/expect_actual.rb +79 -0
- data/lib/rubocop/cop/rspec/file_path.rb +3 -1
- data/lib/rubocop/cop/rspec/focus.rb +12 -28
- data/lib/rubocop/cop/rspec/hook_argument.rb +122 -0
- data/lib/rubocop/cop/rspec/instance_variable.rb +53 -12
- data/lib/rubocop/cop/rspec/leading_subject.rb +58 -0
- data/lib/rubocop/cop/rspec/let_setup.rb +58 -0
- data/lib/rubocop/cop/rspec/message_chain.rb +33 -0
- data/lib/rubocop/cop/rspec/message_expectation.rb +58 -0
- data/lib/rubocop/cop/rspec/multiple_describes.rb +10 -7
- data/lib/rubocop/cop/rspec/multiple_expectations.rb +89 -0
- data/lib/rubocop/cop/rspec/named_subject.rb +10 -1
- data/lib/rubocop/cop/rspec/nested_groups.rb +125 -0
- data/lib/rubocop/cop/rspec/not_to_not.rb +3 -3
- data/lib/rubocop/cop/rspec/subject_stub.rb +136 -0
- data/lib/rubocop/cop/rspec/verified_doubles.rb +4 -1
- data/lib/rubocop/rspec.rb +10 -0
- data/lib/rubocop/rspec/config_formatter.rb +33 -0
- data/lib/rubocop/rspec/description_extractor.rb +35 -0
- data/lib/rubocop/rspec/inject.rb +2 -6
- data/lib/rubocop/rspec/language.rb +73 -0
- data/lib/rubocop/rspec/language/node_pattern.rb +16 -0
- data/lib/rubocop/rspec/spec_only.rb +61 -0
- data/lib/rubocop/rspec/top_level_describe.rb +6 -0
- data/lib/rubocop/rspec/version.rb +1 -1
- data/rubocop-rspec.gemspec +1 -0
- data/spec/project/changelog_spec.rb +81 -0
- data/spec/project/default_config_spec.rb +52 -0
- data/spec/project/project_requires_spec.rb +8 -0
- data/spec/rubocop/cop/rspec/be_eql_spec.rb +59 -0
- data/spec/rubocop/cop/rspec/described_class_spec.rb +2 -2
- data/spec/rubocop/cop/rspec/empty_example_group_spec.rb +79 -0
- data/spec/rubocop/cop/rspec/example_length_spec.rb +50 -30
- data/spec/rubocop/cop/rspec/example_wording_spec.rb +21 -3
- data/spec/rubocop/cop/rspec/expect_actual_spec.rb +136 -0
- data/spec/rubocop/cop/rspec/file_path_spec.rb +48 -71
- data/spec/rubocop/cop/rspec/focus_spec.rb +1 -1
- data/spec/rubocop/cop/rspec/hook_argument_spec.rb +189 -0
- data/spec/rubocop/cop/rspec/instance_variable_spec.rb +37 -0
- data/spec/rubocop/cop/rspec/leading_subject_spec.rb +54 -0
- data/spec/rubocop/cop/rspec/let_setup_spec.rb +66 -0
- data/spec/rubocop/cop/rspec/message_chain_spec.rb +21 -0
- data/spec/rubocop/cop/rspec/message_expectation_spec.rb +63 -0
- data/spec/rubocop/cop/rspec/multiple_expectations_spec.rb +84 -0
- data/spec/rubocop/cop/rspec/nested_groups_spec.rb +55 -0
- data/spec/rubocop/cop/rspec/not_to_not_spec.rb +12 -2
- data/spec/rubocop/cop/rspec/subject_stub_spec.rb +183 -0
- data/spec/rubocop/rspec/config_formatter_spec.rb +48 -0
- data/spec/rubocop/rspec/description_extractor_spec.rb +35 -0
- data/spec/rubocop/rspec/language/selector_set_spec.rb +29 -0
- data/spec/rubocop/rspec/spec_only_spec.rb +97 -0
- data/spec/shared/rspec_only_cop_behavior.rb +68 -0
- data/spec/spec_helper.rb +13 -1
- metadata +72 -5
- data/spec/project_spec.rb +0 -115
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks for `subject` definitions that come after `let` definitions.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# RSpec.describe User do
|
11
|
+
# let(:params) { blah }
|
12
|
+
# subject { described_class.new(params) }
|
13
|
+
#
|
14
|
+
# it 'is valid' do
|
15
|
+
# expect(subject.valid?).to be(true)
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# # good
|
20
|
+
# RSpec.describe User do
|
21
|
+
# subject { described_class.new(params) }
|
22
|
+
#
|
23
|
+
# let(:params) { blah }
|
24
|
+
#
|
25
|
+
# it 'is valid' do
|
26
|
+
# expect(subject.valid?).to be(true)
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
class LeadingSubject < Cop
|
30
|
+
include RuboCop::RSpec::SpecOnly, RuboCop::RSpec::Language
|
31
|
+
|
32
|
+
MSG = 'Declare `subject` above any other `let` declarations'.freeze
|
33
|
+
|
34
|
+
def_node_matcher :subject?, '(block $(send nil :subject ...) args ...)'
|
35
|
+
|
36
|
+
def on_block(node)
|
37
|
+
return unless subject?(node) && !in_spec_block?(node)
|
38
|
+
|
39
|
+
node.parent.each_child_node do |sibling|
|
40
|
+
break if sibling.equal?(node)
|
41
|
+
|
42
|
+
if sibling.method_name.equal?(:let)
|
43
|
+
break add_offense(node, :expression)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def in_spec_block?(node)
|
51
|
+
node.each_ancestor(:block).any? do |ancestor|
|
52
|
+
Examples::ALL.include?(ancestor.method_name)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks unreferenced `let!` calls being used for test setup.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # Bad
|
10
|
+
# let!(:my_widget) { create(:widget) }
|
11
|
+
#
|
12
|
+
# it 'counts widgets' do
|
13
|
+
# expect(Widget.count).to eq(1)
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# # Good
|
17
|
+
# it 'counts widgets' do
|
18
|
+
# create(:widget)
|
19
|
+
# expect(Widget.count).to eq(1)
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# # Good
|
23
|
+
# before { create(:widget) }
|
24
|
+
#
|
25
|
+
# it 'counts widgets' do
|
26
|
+
# expect(Widget.count).to eq(1)
|
27
|
+
# end
|
28
|
+
class LetSetup < Cop
|
29
|
+
include RuboCop::RSpec::SpecOnly,
|
30
|
+
RuboCop::RSpec::TopLevelDescribe,
|
31
|
+
RuboCop::RSpec::Language,
|
32
|
+
RuboCop::RSpec::Language::NodePattern
|
33
|
+
|
34
|
+
MSG = 'Do not use `let!` for test setup.'.freeze
|
35
|
+
|
36
|
+
def_node_search :let_bang, '(block $(send nil :let! (sym $_)) args ...)'
|
37
|
+
|
38
|
+
def_node_search :method_called?, '(send nil %)'
|
39
|
+
|
40
|
+
def on_block(node)
|
41
|
+
return unless example_group?(node)
|
42
|
+
|
43
|
+
unused_let_bang(node) do |let|
|
44
|
+
add_offense(let, :expression)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def unused_let_bang(node)
|
51
|
+
let_bang(node) do |method_send, method_name|
|
52
|
+
yield(method_send) unless method_called?(node, method_name)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module RuboCop
|
2
|
+
module Cop
|
3
|
+
module RSpec
|
4
|
+
# Check that chains of messages are not being stubbed.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# # bad
|
8
|
+
# allow(foo).to receive_message_chain(:bar, :baz).and_return(42)
|
9
|
+
#
|
10
|
+
# # better
|
11
|
+
# thing = Thing.new(baz: 42)
|
12
|
+
# allow(foo).to receive(bar: thing)
|
13
|
+
#
|
14
|
+
class MessageChain < Cop
|
15
|
+
include RuboCop::RSpec::SpecOnly
|
16
|
+
|
17
|
+
MESSAGE = 'Avoid stubbing using `%<method>s`'.freeze
|
18
|
+
|
19
|
+
MESSAGE_CHAIN_METHODS = [
|
20
|
+
:receive_message_chain,
|
21
|
+
:stub_chain
|
22
|
+
].freeze
|
23
|
+
|
24
|
+
def on_send(node)
|
25
|
+
_receiver, method_name, *_args = *node
|
26
|
+
return unless MESSAGE_CHAIN_METHODS.include?(method_name)
|
27
|
+
|
28
|
+
add_offense(node, :selector, MESSAGE % { method: method_name })
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks for consistent message expectation style.
|
7
|
+
#
|
8
|
+
# This cop can be configured in your configuration using the
|
9
|
+
# `EnforcedStyle` option and supports `--auto-gen-config`.
|
10
|
+
#
|
11
|
+
# @example `EnforcedStyle: allow`
|
12
|
+
#
|
13
|
+
# # bad
|
14
|
+
# expect(foo).to receive(:bar)
|
15
|
+
#
|
16
|
+
# # good
|
17
|
+
# allow(foo).to receive(:bar)
|
18
|
+
#
|
19
|
+
# @example `EnforcedStyle: expect`
|
20
|
+
#
|
21
|
+
# # bad
|
22
|
+
# allow(foo).to receive(:bar)
|
23
|
+
#
|
24
|
+
# # good
|
25
|
+
# expect(foo).to receive(:bar)
|
26
|
+
#
|
27
|
+
class MessageExpectation < Cop
|
28
|
+
include RuboCop::RSpec::SpecOnly, ConfigurableEnforcedStyle
|
29
|
+
|
30
|
+
MSG = 'Prefer `%s` for setting message expectations.'.freeze
|
31
|
+
|
32
|
+
SUPPORTED_STYLES = %w(allow expect).freeze
|
33
|
+
|
34
|
+
def_node_matcher :message_expectation, <<-PATTERN
|
35
|
+
(send $(send nil {:expect :allow} ...) :to #receive_message?)
|
36
|
+
PATTERN
|
37
|
+
|
38
|
+
def_node_search :receive_message?, '(send nil :receive ...)'
|
39
|
+
|
40
|
+
def on_send(node)
|
41
|
+
message_expectation(node) do |match|
|
42
|
+
return correct_style_detected if preferred_style?(match)
|
43
|
+
|
44
|
+
add_offense(match, :selector, MSG % style) do
|
45
|
+
opposite_style_detected
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def preferred_style?(expectation)
|
53
|
+
expectation.method_name.equal?(style)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -3,25 +3,28 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module RSpec
|
6
|
-
# Checks for multiple top level describes.
|
7
|
-
#
|
6
|
+
# Checks for multiple top level describes.
|
7
|
+
#
|
8
|
+
# Multiple descriptions for the same class or module should either
|
9
|
+
# be nested or separated into different test files.
|
8
10
|
#
|
9
11
|
# @example
|
10
12
|
# # bad
|
11
|
-
# describe MyClass, '.
|
13
|
+
# describe MyClass, '.do_something' do
|
12
14
|
# end
|
13
|
-
# describe MyClass, '.
|
15
|
+
# describe MyClass, '.do_something_else' do
|
14
16
|
# end
|
15
17
|
#
|
16
18
|
# #good
|
17
19
|
# describe MyClass
|
18
|
-
# describe '.
|
20
|
+
# describe '.do_something' do
|
19
21
|
# end
|
20
|
-
# describe '.
|
22
|
+
# describe '.do_something_else' do
|
21
23
|
# end
|
22
24
|
# end
|
23
25
|
class MultipleDescribes < Cop
|
24
|
-
include RuboCop::RSpec::
|
26
|
+
include RuboCop::RSpec::SpecOnly,
|
27
|
+
RuboCop::RSpec::TopLevelDescribe
|
25
28
|
|
26
29
|
MSG = 'Do not use multiple top level describes - ' \
|
27
30
|
'try to nest them.'.freeze
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks if examples contain too many `expect` calls.
|
7
|
+
#
|
8
|
+
# @see http://betterspecs.org/#single Single expectation test
|
9
|
+
#
|
10
|
+
# This cop is configurable using the `Max` option
|
11
|
+
# and works with `--auto-gen-config`.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
#
|
15
|
+
# # bad
|
16
|
+
# describe UserCreator do
|
17
|
+
# it 'builds a user' do
|
18
|
+
# expect(user.name).to eq("John")
|
19
|
+
# expect(user.age).to eq(22)
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# # good
|
24
|
+
# describe UserCreator do
|
25
|
+
# it 'sets the users name' do
|
26
|
+
# expect(user.name).to eq("John")
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# it 'sets the users age'
|
30
|
+
# expect(user.age).to eq(22)
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# @example configuration
|
35
|
+
#
|
36
|
+
# # .rubocop.yml
|
37
|
+
# RSpec/MultipleExpectations:
|
38
|
+
# Max: 2
|
39
|
+
#
|
40
|
+
# # not flagged by rubocop
|
41
|
+
# describe UserCreator do
|
42
|
+
# it 'builds a user' do
|
43
|
+
# expect(user.name).to eq("John")
|
44
|
+
# expect(user.age).to eq(22)
|
45
|
+
# end
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
class MultipleExpectations < Cop
|
49
|
+
include RuboCop::RSpec::SpecOnly,
|
50
|
+
RuboCop::RSpec::Language,
|
51
|
+
ConfigurableMax
|
52
|
+
|
53
|
+
MSG = 'Too many expectations.'.freeze
|
54
|
+
|
55
|
+
def_node_matcher :example?, <<-PATTERN
|
56
|
+
(block (send _ {#{Examples::ALL.to_node_pattern}} ...) ...)
|
57
|
+
PATTERN
|
58
|
+
|
59
|
+
def_node_search :expect, '(send _ :expect ...)'
|
60
|
+
|
61
|
+
def on_block(node)
|
62
|
+
return unless example?(node) && (expectations = expect(node))
|
63
|
+
|
64
|
+
return if expectations.count <= max_expectations
|
65
|
+
|
66
|
+
self.max = expectations.count
|
67
|
+
|
68
|
+
flag_example(node, expectation_count: expectations.count)
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def flag_example(node, expectation_count:)
|
74
|
+
method, = *node
|
75
|
+
|
76
|
+
add_offense(
|
77
|
+
method,
|
78
|
+
:expression,
|
79
|
+
MSG % { total: expectation_count, max: max_expectations }
|
80
|
+
)
|
81
|
+
end
|
82
|
+
|
83
|
+
def max_expectations
|
84
|
+
Integer(cop_config.fetch(parameter_name, 1))
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -3,7 +3,14 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module RSpec
|
6
|
-
#
|
6
|
+
# Checks for explicitly referenced test subjects.
|
7
|
+
#
|
8
|
+
# RSpec lets you declare an "implicit subject" using `subject { ... }`
|
9
|
+
# which allows for tests like `it { should be_valid }`. If you need to
|
10
|
+
# reference your test subject you should explicitly name it using
|
11
|
+
# `subject(:your_subject_name) { ... }`. Your test subjects should be
|
12
|
+
# the most important object in your tests so they deserve a descriptive
|
13
|
+
# name.
|
7
14
|
#
|
8
15
|
# @example
|
9
16
|
# # bad
|
@@ -31,6 +38,8 @@ module RuboCop
|
|
31
38
|
# it { should be_valid }
|
32
39
|
# end
|
33
40
|
class NamedSubject < Cop
|
41
|
+
include RuboCop::RSpec::SpecOnly
|
42
|
+
|
34
43
|
MSG = 'Name your test subject if '\
|
35
44
|
'you need to reference it explicitly.'.freeze
|
36
45
|
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks for nested example groups.
|
7
|
+
#
|
8
|
+
# This cop is configurable using the `MaxNesting` option
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# # bad
|
12
|
+
# context 'when using some feature' do
|
13
|
+
# let(:some) { :various }
|
14
|
+
# let(:feature) { :setup }
|
15
|
+
#
|
16
|
+
# context 'when user is signed in' do # flagged by rubocop
|
17
|
+
# let(:user) do
|
18
|
+
# UserCreate.call(user_attributes)
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# let(:user_attributes) do
|
22
|
+
# {
|
23
|
+
# name: 'John',
|
24
|
+
# age: 22
|
25
|
+
# role: role
|
26
|
+
# }
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# context 'when user is an admin' do # flagged by rubocop
|
30
|
+
# let(:role) { 'admin' }
|
31
|
+
#
|
32
|
+
# it 'blah blah'
|
33
|
+
# it 'yada yada'
|
34
|
+
# end
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# # better
|
39
|
+
# context 'using some feature as an admin' do
|
40
|
+
# let(:some) { :various }
|
41
|
+
# let(:feature) { :setup }
|
42
|
+
#
|
43
|
+
# let(:user) do
|
44
|
+
# UserCreate.call(
|
45
|
+
# name: 'John',
|
46
|
+
# age: 22
|
47
|
+
# role: 'admin'
|
48
|
+
# )
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# it 'blah blah'
|
52
|
+
# it 'yada yada'
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# @example configuration
|
56
|
+
#
|
57
|
+
# # .rubocop.yml
|
58
|
+
# RSpec/NestedGroups:
|
59
|
+
# MaxNesting: 2
|
60
|
+
#
|
61
|
+
# context 'when using some feature' do
|
62
|
+
# let(:some) { :various }
|
63
|
+
# let(:feature) { :setup }
|
64
|
+
#
|
65
|
+
# context 'when user is signed in' do
|
66
|
+
# let(:user) do
|
67
|
+
# UserCreate.call(user_attributes)
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# let(:user_attributes) do
|
71
|
+
# {
|
72
|
+
# name: 'John',
|
73
|
+
# age: 22
|
74
|
+
# role: role
|
75
|
+
# }
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
# context 'when user is an admin' do # flagged by rubocop
|
79
|
+
# let(:role) { 'admin' }
|
80
|
+
#
|
81
|
+
# it 'blah blah'
|
82
|
+
# it 'yada yada'
|
83
|
+
# end
|
84
|
+
# end
|
85
|
+
# end
|
86
|
+
#
|
87
|
+
class NestedGroups < Cop
|
88
|
+
include RuboCop::RSpec::SpecOnly,
|
89
|
+
RuboCop::RSpec::TopLevelDescribe,
|
90
|
+
RuboCop::RSpec::Language
|
91
|
+
|
92
|
+
MSG = 'Maximum example group nesting exceeded'.freeze
|
93
|
+
|
94
|
+
def_node_search :find_contexts, <<-PATTERN
|
95
|
+
(block (send nil {#{ExampleGroups::ALL.to_node_pattern}} ...) (args) ...)
|
96
|
+
PATTERN
|
97
|
+
|
98
|
+
def on_block(node)
|
99
|
+
describe, = described_constant(node)
|
100
|
+
return unless describe
|
101
|
+
|
102
|
+
find_nested_contexts(node) do |context|
|
103
|
+
add_offense(context.children.first, :expression)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def find_nested_contexts(node, nesting: 1, &block)
|
110
|
+
find_contexts(node) do |nested_context|
|
111
|
+
yield(nested_context) if nesting > max_nesting
|
112
|
+
|
113
|
+
nested_context.each_child_node do |child|
|
114
|
+
find_nested_contexts(child, nesting: nesting + 1, &block)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def max_nesting
|
120
|
+
Integer(cop_config.fetch('MaxNesting', 2))
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|