rubocop-rspec 1.6.0 → 1.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|