File: stubbed_mock.rb

package info (click to toggle)
ruby-rubocop-rspec 2.16.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 1,892 kB
  • sloc: ruby: 22,283; makefile: 4
file content (174 lines) | stat: -rw-r--r-- 5,962 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# frozen_string_literal: true

module RuboCop
  module Cop
    module RSpec
      # Checks that message expectations do not have a configured response.
      #
      # @example
      #   # bad
      #   expect(foo).to receive(:bar).with(42).and_return("hello world")
      #
      #   # good (without spies)
      #   allow(foo).to receive(:bar).with(42).and_return("hello world")
      #   expect(foo).to receive(:bar).with(42)
      #
      class StubbedMock < Base
        MSG = 'Prefer `%<replacement>s` over `%<method_name>s` when ' \
              'configuring a response.'

        # @!method message_expectation?(node)
        #   Match message expectation matcher
        #
        #   @example source that matches
        #     receive(:foo)
        #
        #   @example source that matches
        #     receive_message_chain(:foo, :bar)
        #
        #   @example source that matches
        #     receive(:foo).with('bar')
        #
        #   @param node [RuboCop::AST::Node]
        #   @return [Array<RuboCop::AST::Node>] matching nodes
        def_node_matcher :message_expectation?, <<-PATTERN
          {
            (send nil? { :receive :receive_message_chain } ...)  # receive(:foo)
            (send (send nil? :receive ...) :with ...)            # receive(:foo).with('bar')
          }
        PATTERN

        # @!method configured_response?(node)
        def_node_matcher :configured_response?, <<~PATTERN
          { :and_return :and_raise :and_throw :and_yield
            :and_call_original :and_wrap_original }
        PATTERN

        # @!method expectation(node)
        #   Match expectation
        #
        #   @example source that matches
        #     is_expected.to be_in_the_bar
        #
        #   @example source that matches
        #     expect(cocktail).to contain_exactly(:fresh_orange_juice, :campari)
        #
        #   @example source that matches
        #     expect_any_instance_of(Officer).to be_alert
        #
        #   @param node [RuboCop::AST::Node]
        #   @yield [RuboCop::AST::Node] expectation, method name, matcher
        def_node_matcher :expectation, <<~PATTERN
          (send
            $(send nil? $#Expectations.all ...)
            :to $_)
        PATTERN

        # @!method matcher_with_configured_response(node)
        #   Match matcher with a configured response
        #
        #   @example source that matches
        #     receive(:foo).and_return('bar')
        #
        #   @example source that matches
        #     receive(:lower).and_raise(SomeError)
        #
        #   @example source that matches
        #     receive(:redirect).and_call_original
        #
        #   @param node [RuboCop::AST::Node]
        #   @yield [RuboCop::AST::Node] matcher
        def_node_matcher :matcher_with_configured_response, <<~PATTERN
          (send #message_expectation? #configured_response? _)
        PATTERN

        # @!method matcher_with_return_block(node)
        #   Match matcher with a return block
        #
        #   @example source that matches
        #     receive(:foo) { 'bar' }
        #
        #   @param node [RuboCop::AST::Node]
        #   @yield [RuboCop::AST::Node] matcher
        def_node_matcher :matcher_with_return_block, <<~PATTERN
          (block #message_expectation? args _)  # receive(:foo) { 'bar' }
        PATTERN

        # @!method matcher_with_hash(node)
        #   Match matcher with a configured response defined as a hash
        #
        #   @example source that matches
        #     receive_messages(foo: 'bar', baz: 'qux')
        #
        #   @example source that matches
        #     receive_message_chain(:foo, bar: 'baz')
        #
        #   @param node [RuboCop::AST::Node]
        #   @yield [RuboCop::AST::Node] matcher
        def_node_matcher :matcher_with_hash, <<~PATTERN
          {
            (send nil? :receive_messages hash)           # receive_messages(foo: 'bar', baz: 'qux')
            (send nil? :receive_message_chain ... hash)  # receive_message_chain(:foo, bar: 'baz')
          }
        PATTERN

        # @!method matcher_with_blockpass(node)
        #   Match matcher with a configured response in block-pass
        #
        #   @example source that matches
        #     receive(:foo, &canned)
        #
        #   @example source that matches
        #     receive_message_chain(:foo, :bar, &canned)
        #
        #   @example source that matches
        #     receive(:foo).with('bar', &canned)
        #
        #   @param node [RuboCop::AST::Node]
        #   @yield [RuboCop::AST::Node] matcher
        def_node_matcher :matcher_with_blockpass, <<~PATTERN
          {
            (send nil? { :receive :receive_message_chain } ... block_pass)  # receive(:foo, &canned)
            (send (send nil? :receive ...) :with ... block_pass)            # receive(:foo).with('foo', &canned)
          }
        PATTERN

        RESTRICT_ON_SEND = %i[to].freeze

        def on_send(node)
          expectation(node, &method(:on_expectation))
        end

        private

        def on_expectation(expectation, method_name, matcher)
          flag_expectation = lambda do
            add_offense(expectation, message: msg(method_name))
          end

          matcher_with_configured_response(matcher, &flag_expectation)
          matcher_with_return_block(matcher, &flag_expectation)
          matcher_with_hash(matcher, &flag_expectation)
          matcher_with_blockpass(matcher, &flag_expectation)
        end

        def msg(method_name)
          format(MSG,
                 method_name: method_name,
                 replacement: replacement(method_name))
        end

        def replacement(method_name)
          case method_name
          when :expect
            :allow
          when :is_expected
            'allow(subject)'
          when :expect_any_instance_of
            :allow_any_instance_of
          end
        end
      end
    end
  end
end