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.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/README.md +23 -0
  4. data/Rakefile +19 -1
  5. data/config/default.yml +78 -15
  6. data/lib/rubocop-rspec.rb +19 -1
  7. data/lib/rubocop/cop/rspec/any_instance.rb +5 -1
  8. data/lib/rubocop/cop/rspec/be_eql.rb +58 -0
  9. data/lib/rubocop/cop/rspec/describe_class.rb +2 -3
  10. data/lib/rubocop/cop/rspec/describe_method.rb +4 -3
  11. data/lib/rubocop/cop/rspec/described_class.rb +4 -35
  12. data/lib/rubocop/cop/rspec/empty_example_group.rb +100 -0
  13. data/lib/rubocop/cop/rspec/example_length.rb +5 -2
  14. data/lib/rubocop/cop/rspec/example_wording.rb +5 -2
  15. data/lib/rubocop/cop/rspec/expect_actual.rb +79 -0
  16. data/lib/rubocop/cop/rspec/file_path.rb +3 -1
  17. data/lib/rubocop/cop/rspec/focus.rb +12 -28
  18. data/lib/rubocop/cop/rspec/hook_argument.rb +122 -0
  19. data/lib/rubocop/cop/rspec/instance_variable.rb +53 -12
  20. data/lib/rubocop/cop/rspec/leading_subject.rb +58 -0
  21. data/lib/rubocop/cop/rspec/let_setup.rb +58 -0
  22. data/lib/rubocop/cop/rspec/message_chain.rb +33 -0
  23. data/lib/rubocop/cop/rspec/message_expectation.rb +58 -0
  24. data/lib/rubocop/cop/rspec/multiple_describes.rb +10 -7
  25. data/lib/rubocop/cop/rspec/multiple_expectations.rb +89 -0
  26. data/lib/rubocop/cop/rspec/named_subject.rb +10 -1
  27. data/lib/rubocop/cop/rspec/nested_groups.rb +125 -0
  28. data/lib/rubocop/cop/rspec/not_to_not.rb +3 -3
  29. data/lib/rubocop/cop/rspec/subject_stub.rb +136 -0
  30. data/lib/rubocop/cop/rspec/verified_doubles.rb +4 -1
  31. data/lib/rubocop/rspec.rb +10 -0
  32. data/lib/rubocop/rspec/config_formatter.rb +33 -0
  33. data/lib/rubocop/rspec/description_extractor.rb +35 -0
  34. data/lib/rubocop/rspec/inject.rb +2 -6
  35. data/lib/rubocop/rspec/language.rb +73 -0
  36. data/lib/rubocop/rspec/language/node_pattern.rb +16 -0
  37. data/lib/rubocop/rspec/spec_only.rb +61 -0
  38. data/lib/rubocop/rspec/top_level_describe.rb +6 -0
  39. data/lib/rubocop/rspec/version.rb +1 -1
  40. data/rubocop-rspec.gemspec +1 -0
  41. data/spec/project/changelog_spec.rb +81 -0
  42. data/spec/project/default_config_spec.rb +52 -0
  43. data/spec/project/project_requires_spec.rb +8 -0
  44. data/spec/rubocop/cop/rspec/be_eql_spec.rb +59 -0
  45. data/spec/rubocop/cop/rspec/described_class_spec.rb +2 -2
  46. data/spec/rubocop/cop/rspec/empty_example_group_spec.rb +79 -0
  47. data/spec/rubocop/cop/rspec/example_length_spec.rb +50 -30
  48. data/spec/rubocop/cop/rspec/example_wording_spec.rb +21 -3
  49. data/spec/rubocop/cop/rspec/expect_actual_spec.rb +136 -0
  50. data/spec/rubocop/cop/rspec/file_path_spec.rb +48 -71
  51. data/spec/rubocop/cop/rspec/focus_spec.rb +1 -1
  52. data/spec/rubocop/cop/rspec/hook_argument_spec.rb +189 -0
  53. data/spec/rubocop/cop/rspec/instance_variable_spec.rb +37 -0
  54. data/spec/rubocop/cop/rspec/leading_subject_spec.rb +54 -0
  55. data/spec/rubocop/cop/rspec/let_setup_spec.rb +66 -0
  56. data/spec/rubocop/cop/rspec/message_chain_spec.rb +21 -0
  57. data/spec/rubocop/cop/rspec/message_expectation_spec.rb +63 -0
  58. data/spec/rubocop/cop/rspec/multiple_expectations_spec.rb +84 -0
  59. data/spec/rubocop/cop/rspec/nested_groups_spec.rb +55 -0
  60. data/spec/rubocop/cop/rspec/not_to_not_spec.rb +12 -2
  61. data/spec/rubocop/cop/rspec/subject_stub_spec.rb +183 -0
  62. data/spec/rubocop/rspec/config_formatter_spec.rb +48 -0
  63. data/spec/rubocop/rspec/description_extractor_spec.rb +35 -0
  64. data/spec/rubocop/rspec/language/selector_set_spec.rb +29 -0
  65. data/spec/rubocop/rspec/spec_only_spec.rb +97 -0
  66. data/spec/shared/rspec_only_cop_behavior.rb +68 -0
  67. data/spec/spec_helper.rb +13 -1
  68. metadata +72 -5
  69. 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. They should be nested if it is
7
- # for the same class or module or seperated into different files.
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, '.do_someting' do
13
+ # describe MyClass, '.do_something' do
12
14
  # end
13
- # describe MyClass, '.do_someting_else' do
15
+ # describe MyClass, '.do_something_else' do
14
16
  # end
15
17
  #
16
18
  # #good
17
19
  # describe MyClass
18
- # describe '.do_someting' do
20
+ # describe '.do_something' do
19
21
  # end
20
- # describe '.do_someting_else' do
22
+ # describe '.do_something_else' do
21
23
  # end
22
24
  # end
23
25
  class MultipleDescribes < Cop
24
- include RuboCop::RSpec::TopLevelDescribe
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
- # Give `subject` a descriptive name if you reference it directly
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