File: receive.rb

package info (click to toggle)
ruby-rspec 3.13.0c0e0m0s1-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 6,856 kB
  • sloc: ruby: 70,868; sh: 1,423; makefile: 99
file content (134 lines) | stat: -rw-r--r-- 4,484 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
RSpec::Support.require_rspec_mocks 'matchers/expectation_customization'

module RSpec
  module Mocks
    module Matchers
      # @private
      class Receive
        include Matcher

        def initialize(message, block)
          @message                 = message
          @block                   = block
          @recorded_customizations = []
        end

        def matcher_name
          "receive"
        end

        def description
          describable.description_for("receive")
        end

        def setup_expectation(subject, &block)
          warn_if_any_instance("expect", subject)
          @describable = setup_mock_proxy_method_substitute(subject, :add_message_expectation, block)
        end
        alias matches? setup_expectation

        def setup_negative_expectation(subject, &block)
          # ensure `never` goes first for cases like `never.and_return(5)`,
          # where `and_return` is meant to raise an error
          @recorded_customizations.unshift ExpectationCustomization.new(:never, [], nil)

          warn_if_any_instance("expect", subject)

          setup_expectation(subject, &block)
        end
        alias does_not_match? setup_negative_expectation

        def setup_allowance(subject, &block)
          warn_if_any_instance("allow", subject)
          setup_mock_proxy_method_substitute(subject, :add_stub, block)
        end

        def setup_any_instance_expectation(subject, &block)
          setup_any_instance_method_substitute(subject, :should_receive, block)
        end

        def setup_any_instance_negative_expectation(subject, &block)
          setup_any_instance_method_substitute(subject, :should_not_receive, block)
        end

        def setup_any_instance_allowance(subject, &block)
          setup_any_instance_method_substitute(subject, :stub, block)
        end

        own_methods = (instance_methods - superclass.instance_methods)
        MessageExpectation.public_instance_methods(false).each do |method|
          next if own_methods.include?(method)

          define_method(method) do |*args, &block|
            @recorded_customizations << ExpectationCustomization.new(method, args, block)
            self
          end
          ruby2_keywords(method) if respond_to?(:ruby2_keywords, true)
        end

      private

        def describable
          @describable ||= DefaultDescribable.new(@message)
        end

        def warn_if_any_instance(expression, subject)
          return unless AnyInstance::Proxy === subject

          RSpec.warning(
            "`#{expression}(#{subject.klass}.any_instance).to` " \
            "is probably not what you meant, it does not operate on " \
            "any instance of `#{subject.klass}`. " \
            "Use `#{expression}_any_instance_of(#{subject.klass}).to` instead."
          )
        end

        def setup_mock_proxy_method_substitute(subject, method, block)
          proxy = ::RSpec::Mocks.space.proxy_for(subject)
          setup_method_substitute(proxy, method, block)
        end

        def setup_any_instance_method_substitute(subject, method, block)
          proxy = ::RSpec::Mocks.space.any_instance_proxy_for(subject)
          setup_method_substitute(proxy, method, block)
        end

        def setup_method_substitute(host, method, block, *args)
          args << @message.to_sym
          block = move_block_to_last_customization(block)

          expectation = host.__send__(method, *args, &(@block || block))

          @recorded_customizations.each do |customization|
            customization.playback_onto(expectation)
          end
          expectation
        end

        def move_block_to_last_customization(block)
          last = @recorded_customizations.last
          return block unless last

          last.block ||= block
          nil
        end

        # MessageExpectation objects are able to describe themselves in detail.
        # We use this as a fall back when a MessageExpectation is not available.
        # @private
        class DefaultDescribable
          def initialize(message)
            @message = message
          end

          # This is much simpler for the `any_instance` case than what the
          # user may want, but I'm not up for putting a bunch of effort
          # into full descriptions for `any_instance` expectations at this point :(.
          def description_for(verb)
            "#{verb} #{@message}"
          end
        end
      end
    end
  end
end