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.
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