File: proxy.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 (125 lines) | stat: -rw-r--r-- 4,771 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
module RSpec
  module Mocks
    module AnyInstance
      # @private
      # The `AnyInstance::Recorder` is responsible for redefining the klass's
      # instance method in order to add any stubs/expectations the first time
      # the method is called. It's not capable of updating a stub on an instance
      # that's already been previously stubbed (either directly, or via
      # `any_instance`).
      #
      # This proxy sits in front of the recorder and delegates both to it
      # and to the `RSpec::Mocks::Proxy` for each already mocked or stubbed
      # instance of the class, in order to propagates changes to the instances.
      #
      # Note that unlike `RSpec::Mocks::Proxy`, this proxy class is stateless
      # and is not persisted in `RSpec::Mocks.space`.
      #
      # Proxying for the message expectation fluent interface (typically chained
      # off of the return value of one of these methods) is provided by the
      # `FluentInterfaceProxy` class below.
      class Proxy
        def initialize(recorder, target_proxies)
          @recorder       = recorder
          @target_proxies = target_proxies
        end

        def klass
          @recorder.klass
        end

        def stub(method_name_or_method_map, &block)
          if Hash === method_name_or_method_map
            method_name_or_method_map.each do |method_name, return_value|
              stub(method_name).and_return(return_value)
            end
          else
            perform_proxying(__method__, [method_name_or_method_map], block) do |proxy|
              proxy.add_stub(method_name_or_method_map, &block)
            end
          end
        end

        def unstub(method_name)
          perform_proxying(__method__, [method_name], nil) do |proxy|
            proxy.remove_stub_if_present(method_name)
          end
        end

        def stub_chain(*chain, &block)
          perform_proxying(__method__, chain, block) do |proxy|
            Mocks::StubChain.stub_chain_on(proxy.object, *chain, &block)
          end
        end

        def expect_chain(*chain, &block)
          perform_proxying(__method__, chain, block) do |proxy|
            Mocks::ExpectChain.expect_chain_on(proxy.object, *chain, &block)
          end
        end

        def should_receive(method_name, &block)
          perform_proxying(__method__, [method_name], block) do |proxy|
            # Yeah, this is a bit odd...but if we used `add_message_expectation`
            # then it would act like `expect_every_instance_of(klass).to receive`.
            # The any_instance recorder takes care of validating that an instance
            # received the message.
            proxy.add_stub(method_name, &block)
          end
        end

        def should_not_receive(method_name, &block)
          perform_proxying(__method__, [method_name], block) do |proxy|
            proxy.add_message_expectation(method_name, &block).never
          end
        end

      private

        def perform_proxying(method_name, args, block, &target_proxy_block)
          recorder_value = @recorder.__send__(method_name, *args, &block)
          proxy_values   = @target_proxies.map(&target_proxy_block)
          FluentInterfaceProxy.new([recorder_value] + proxy_values)
        end
      end

      unless defined?(BasicObject)
        class BasicObject
          # Remove all methods except those expected to be defined on BasicObject
          (instance_methods.map(&:to_sym) - [:__send__, :"!", :instance_eval, :==, :instance_exec, :"!=", :equal?, :__id__, :__binding__, :object_id]).each do |method|
            undef_method method
          end
        end
      end

      # @private
      # Delegates messages to each of the given targets in order to
      # provide the fluent interface that is available off of message
      # expectations when dealing with `any_instance`.
      #
      # `targets` will typically contain 1 of the `AnyInstance::Recorder`
      # return values and N `MessageExpectation` instances (one per instance
      # of the `any_instance` klass).
      class FluentInterfaceProxy < BasicObject
        def initialize(targets)
          @targets = targets
        end

        if ::RUBY_VERSION.to_f > 1.8
          def respond_to_missing?(method_name, include_private=false)
            super || @targets.first.respond_to?(method_name, include_private)
          end
        else
          def respond_to?(method_name, include_private=false)
            super || @targets.first.respond_to?(method_name, include_private)
          end
        end

        def method_missing(*args, &block)
          return_values = @targets.map { |t| t.__send__(*args, &block) }
          FluentInterfaceProxy.new(return_values)
        end
      end
    end
  end
end