rspec-mocks 3.8.1

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 (51) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +3 -0
  4. data/.document +5 -0
  5. data/.yardopts +6 -0
  6. data/Changelog.md +1108 -0
  7. data/LICENSE.md +25 -0
  8. data/README.md +460 -0
  9. data/lib/rspec/mocks.rb +130 -0
  10. data/lib/rspec/mocks/any_instance.rb +11 -0
  11. data/lib/rspec/mocks/any_instance/chain.rb +110 -0
  12. data/lib/rspec/mocks/any_instance/error_generator.rb +31 -0
  13. data/lib/rspec/mocks/any_instance/expect_chain_chain.rb +31 -0
  14. data/lib/rspec/mocks/any_instance/expectation_chain.rb +50 -0
  15. data/lib/rspec/mocks/any_instance/message_chains.rb +83 -0
  16. data/lib/rspec/mocks/any_instance/proxy.rb +116 -0
  17. data/lib/rspec/mocks/any_instance/recorder.rb +289 -0
  18. data/lib/rspec/mocks/any_instance/stub_chain.rb +51 -0
  19. data/lib/rspec/mocks/any_instance/stub_chain_chain.rb +23 -0
  20. data/lib/rspec/mocks/argument_list_matcher.rb +100 -0
  21. data/lib/rspec/mocks/argument_matchers.rb +320 -0
  22. data/lib/rspec/mocks/configuration.rb +212 -0
  23. data/lib/rspec/mocks/error_generator.rb +369 -0
  24. data/lib/rspec/mocks/example_methods.rb +434 -0
  25. data/lib/rspec/mocks/instance_method_stasher.rb +146 -0
  26. data/lib/rspec/mocks/marshal_extension.rb +41 -0
  27. data/lib/rspec/mocks/matchers/expectation_customization.rb +20 -0
  28. data/lib/rspec/mocks/matchers/have_received.rb +134 -0
  29. data/lib/rspec/mocks/matchers/receive.rb +132 -0
  30. data/lib/rspec/mocks/matchers/receive_message_chain.rb +82 -0
  31. data/lib/rspec/mocks/matchers/receive_messages.rb +77 -0
  32. data/lib/rspec/mocks/message_chain.rb +87 -0
  33. data/lib/rspec/mocks/message_expectation.rb +741 -0
  34. data/lib/rspec/mocks/method_double.rb +287 -0
  35. data/lib/rspec/mocks/method_reference.rb +202 -0
  36. data/lib/rspec/mocks/minitest_integration.rb +68 -0
  37. data/lib/rspec/mocks/mutate_const.rb +339 -0
  38. data/lib/rspec/mocks/object_reference.rb +149 -0
  39. data/lib/rspec/mocks/order_group.rb +81 -0
  40. data/lib/rspec/mocks/proxy.rb +485 -0
  41. data/lib/rspec/mocks/space.rb +238 -0
  42. data/lib/rspec/mocks/standalone.rb +3 -0
  43. data/lib/rspec/mocks/syntax.rb +325 -0
  44. data/lib/rspec/mocks/targets.rb +124 -0
  45. data/lib/rspec/mocks/test_double.rb +171 -0
  46. data/lib/rspec/mocks/verifying_double.rb +129 -0
  47. data/lib/rspec/mocks/verifying_message_expectation.rb +54 -0
  48. data/lib/rspec/mocks/verifying_proxy.rb +220 -0
  49. data/lib/rspec/mocks/version.rb +9 -0
  50. metadata +221 -0
  51. metadata.gz.sig +0 -0
@@ -0,0 +1,116 @@
1
+ module RSpec
2
+ module Mocks
3
+ module AnyInstance
4
+ # @private
5
+ # The `AnyInstance::Recorder` is responsible for redefining the klass's
6
+ # instance method in order to add any stubs/expectations the first time
7
+ # the method is called. It's not capable of updating a stub on an instance
8
+ # that's already been previously stubbed (either directly, or via
9
+ # `any_instance`).
10
+ #
11
+ # This proxy sits in front of the recorder and delegates both to it
12
+ # and to the `RSpec::Mocks::Proxy` for each already mocked or stubbed
13
+ # instance of the class, in order to propogates changes to the instances.
14
+ #
15
+ # Note that unlike `RSpec::Mocks::Proxy`, this proxy class is stateless
16
+ # and is not persisted in `RSpec::Mocks.space`.
17
+ #
18
+ # Proxying for the message expectation fluent interface (typically chained
19
+ # off of the return value of one of these methods) is provided by the
20
+ # `FluentInterfaceProxy` class below.
21
+ class Proxy
22
+ def initialize(recorder, target_proxies)
23
+ @recorder = recorder
24
+ @target_proxies = target_proxies
25
+ end
26
+
27
+ def klass
28
+ @recorder.klass
29
+ end
30
+
31
+ def stub(method_name_or_method_map, &block)
32
+ if Hash === method_name_or_method_map
33
+ method_name_or_method_map.each do |method_name, return_value|
34
+ stub(method_name).and_return(return_value)
35
+ end
36
+ else
37
+ perform_proxying(__method__, [method_name_or_method_map], block) do |proxy|
38
+ proxy.add_stub(method_name_or_method_map, &block)
39
+ end
40
+ end
41
+ end
42
+
43
+ def unstub(method_name)
44
+ perform_proxying(__method__, [method_name], nil) do |proxy|
45
+ proxy.remove_stub_if_present(method_name)
46
+ end
47
+ end
48
+
49
+ def stub_chain(*chain, &block)
50
+ perform_proxying(__method__, chain, block) do |proxy|
51
+ Mocks::StubChain.stub_chain_on(proxy.object, *chain, &block)
52
+ end
53
+ end
54
+
55
+ def expect_chain(*chain, &block)
56
+ perform_proxying(__method__, chain, block) do |proxy|
57
+ Mocks::ExpectChain.expect_chain_on(proxy.object, *chain, &block)
58
+ end
59
+ end
60
+
61
+ def should_receive(method_name, &block)
62
+ perform_proxying(__method__, [method_name], block) do |proxy|
63
+ # Yeah, this is a bit odd...but if we used `add_message_expectation`
64
+ # then it would act like `expect_every_instance_of(klass).to receive`.
65
+ # The any_instance recorder takes care of validating that an instance
66
+ # received the message.
67
+ proxy.add_stub(method_name, &block)
68
+ end
69
+ end
70
+
71
+ def should_not_receive(method_name, &block)
72
+ perform_proxying(__method__, [method_name], block) do |proxy|
73
+ proxy.add_message_expectation(method_name, &block).never
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ def perform_proxying(method_name, args, block, &target_proxy_block)
80
+ recorder_value = @recorder.__send__(method_name, *args, &block)
81
+ proxy_values = @target_proxies.map(&target_proxy_block)
82
+ FluentInterfaceProxy.new([recorder_value] + proxy_values)
83
+ end
84
+ end
85
+
86
+ # @private
87
+ # Delegates messages to each of the given targets in order to
88
+ # provide the fluent interface that is available off of message
89
+ # expectations when dealing with `any_instance`.
90
+ #
91
+ # `targets` will typically contain 1 of the `AnyInstance::Recorder`
92
+ # return values and N `MessageExpectation` instances (one per instance
93
+ # of the `any_instance` klass).
94
+ class FluentInterfaceProxy
95
+ def initialize(targets)
96
+ @targets = targets
97
+ end
98
+
99
+ if RUBY_VERSION.to_f > 1.8
100
+ def respond_to_missing?(method_name, include_private=false)
101
+ super || @targets.first.respond_to?(method_name, include_private)
102
+ end
103
+ else
104
+ def respond_to?(method_name, include_private=false)
105
+ super || @targets.first.respond_to?(method_name, include_private)
106
+ end
107
+ end
108
+
109
+ def method_missing(*args, &block)
110
+ return_values = @targets.map { |t| t.__send__(*args, &block) }
111
+ FluentInterfaceProxy.new(return_values)
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,289 @@
1
+ module RSpec
2
+ module Mocks
3
+ module AnyInstance
4
+ # Given a class `TheClass`, `TheClass.any_instance` returns a `Recorder`,
5
+ # which records stubs and message expectations for later playback on
6
+ # instances of `TheClass`.
7
+ #
8
+ # Further constraints are stored in instances of [Chain](Chain).
9
+ #
10
+ # @see AnyInstance
11
+ # @see Chain
12
+ class Recorder
13
+ # @private
14
+ attr_reader :message_chains, :stubs, :klass
15
+
16
+ def initialize(klass)
17
+ @message_chains = MessageChains.new
18
+ @stubs = Hash.new { |hash, key| hash[key] = [] }
19
+ @observed_methods = []
20
+ @played_methods = {}
21
+ @backed_up_method_owner = {}
22
+ @klass = klass
23
+ @expectation_set = false
24
+ end
25
+
26
+ # Initializes the recording a stub to be played back against any
27
+ # instance of this object that invokes the submitted method.
28
+ #
29
+ # @see Methods#stub
30
+ def stub(method_name, &block)
31
+ observe!(method_name)
32
+ message_chains.add(method_name, StubChain.new(self, method_name, &block))
33
+ end
34
+
35
+ # Initializes the recording a stub chain to be played back against any
36
+ # instance of this object that invokes the method matching the first
37
+ # argument.
38
+ #
39
+ # @see Methods#stub_chain
40
+ def stub_chain(*method_names_and_optional_return_values, &block)
41
+ normalize_chain(*method_names_and_optional_return_values) do |method_name, args|
42
+ observe!(method_name)
43
+ message_chains.add(method_name, StubChainChain.new(self, *args, &block))
44
+ end
45
+ end
46
+
47
+ # @private
48
+ def expect_chain(*method_names_and_optional_return_values, &block)
49
+ @expectation_set = true
50
+ normalize_chain(*method_names_and_optional_return_values) do |method_name, args|
51
+ observe!(method_name)
52
+ message_chains.add(method_name, ExpectChainChain.new(self, *args, &block))
53
+ end
54
+ end
55
+
56
+ # Initializes the recording a message expectation to be played back
57
+ # against any instance of this object that invokes the submitted
58
+ # method.
59
+ #
60
+ # @see Methods#should_receive
61
+ def should_receive(method_name, &block)
62
+ @expectation_set = true
63
+ observe!(method_name)
64
+ message_chains.add(method_name, PositiveExpectationChain.new(self, method_name, &block))
65
+ end
66
+
67
+ # The opposite of `should_receive`
68
+ #
69
+ # @see Methods#should_not_receive
70
+ def should_not_receive(method_name, &block)
71
+ should_receive(method_name, &block).never
72
+ end
73
+
74
+ # Removes any previously recorded stubs, stub_chains or message
75
+ # expectations that use `method_name`.
76
+ #
77
+ # @see Methods#unstub
78
+ def unstub(method_name)
79
+ unless @observed_methods.include?(method_name.to_sym)
80
+ AnyInstance.error_generator.raise_method_not_stubbed_error(method_name)
81
+ end
82
+ message_chains.remove_stub_chains_for!(method_name)
83
+ stubs[method_name].clear
84
+ stop_observing!(method_name) unless message_chains.has_expectation?(method_name)
85
+ end
86
+
87
+ # @api private
88
+ #
89
+ # Used internally to verify that message expectations have been
90
+ # fulfilled.
91
+ def verify
92
+ return unless @expectation_set
93
+ return if message_chains.all_expectations_fulfilled?
94
+
95
+ AnyInstance.error_generator.raise_second_instance_received_message_error(message_chains.unfulfilled_expectations)
96
+ end
97
+
98
+ # @private
99
+ def stop_all_observation!
100
+ @observed_methods.each { |method_name| restore_method!(method_name) }
101
+ end
102
+
103
+ # @private
104
+ def playback!(instance, method_name)
105
+ RSpec::Mocks.space.ensure_registered(instance)
106
+ message_chains.playback!(instance, method_name)
107
+ @played_methods[method_name] = instance
108
+ received_expected_message!(method_name) if message_chains.has_expectation?(method_name)
109
+ end
110
+
111
+ # @private
112
+ def instance_that_received(method_name)
113
+ @played_methods[method_name]
114
+ end
115
+
116
+ # @private
117
+ def build_alias_method_name(method_name)
118
+ "__#{method_name}_without_any_instance__"
119
+ end
120
+
121
+ # @private
122
+ def already_observing?(method_name)
123
+ @observed_methods.include?(method_name) || super_class_observing?(method_name)
124
+ end
125
+
126
+ # @private
127
+ def notify_received_message(_object, message, args, _blk)
128
+ has_expectation = false
129
+
130
+ message_chains.each_unfulfilled_expectation_matching(message, *args) do |expectation|
131
+ has_expectation = true
132
+ expectation.expectation_fulfilled!
133
+ end
134
+
135
+ return unless has_expectation
136
+
137
+ restore_method!(message)
138
+ mark_invoked!(message)
139
+ end
140
+
141
+ protected
142
+
143
+ def stop_observing!(method_name)
144
+ restore_method!(method_name)
145
+ @observed_methods.delete(method_name)
146
+ super_class_observers_for(method_name).each do |ancestor|
147
+ ::RSpec::Mocks.space.
148
+ any_instance_recorder_for(ancestor).stop_observing!(method_name)
149
+ end
150
+ end
151
+
152
+ private
153
+
154
+ def ancestor_is_an_observer?(method_name)
155
+ lambda do |ancestor|
156
+ unless ancestor == @klass
157
+ ::RSpec::Mocks.space.
158
+ any_instance_recorder_for(ancestor).already_observing?(method_name)
159
+ end
160
+ end
161
+ end
162
+
163
+ def super_class_observers_for(method_name)
164
+ @klass.ancestors.select(&ancestor_is_an_observer?(method_name))
165
+ end
166
+
167
+ def super_class_observing?(method_name)
168
+ @klass.ancestors.any?(&ancestor_is_an_observer?(method_name))
169
+ end
170
+
171
+ def normalize_chain(*args)
172
+ args.shift.to_s.split('.').map { |s| s.to_sym }.reverse.each { |a| args.unshift a }
173
+ yield args.first, args
174
+ end
175
+
176
+ def received_expected_message!(method_name)
177
+ message_chains.received_expected_message!(method_name)
178
+ restore_method!(method_name)
179
+ mark_invoked!(method_name)
180
+ end
181
+
182
+ def restore_method!(method_name)
183
+ if public_protected_or_private_method_defined?(build_alias_method_name(method_name))
184
+ restore_original_method!(method_name)
185
+ else
186
+ remove_dummy_method!(method_name)
187
+ end
188
+ end
189
+
190
+ def restore_original_method!(method_name)
191
+ return unless @klass.instance_method(method_name).owner == @klass
192
+
193
+ alias_method_name = build_alias_method_name(method_name)
194
+ @klass.class_exec(@backed_up_method_owner) do |backed_up_method_owner|
195
+ remove_method method_name
196
+
197
+ # A @klass can have methods implemented (see Method#owner) in @klass
198
+ # or inherited from a superclass. In ruby 2.2 and earlier, we can copy
199
+ # a method regardless of the 'owner' and restore it to @klass after
200
+ # because a call to 'super' from @klass's copied method would end up
201
+ # calling the original class's superclass's method.
202
+ #
203
+ # With the commit below, available starting in 2.3.0, ruby changed
204
+ # this behavior and a call to 'super' from the method copied to @klass
205
+ # will call @klass's superclass method, which is the original
206
+ # implementer of this method! This leads to very strange errors
207
+ # if @klass's copied method calls 'super', since it would end up
208
+ # calling itself, the original method implemented in @klass's
209
+ # superclass.
210
+ #
211
+ # For ruby 2.3 and above, we need to only restore methods that
212
+ # @klass originally owned.
213
+ #
214
+ # https://github.com/ruby/ruby/commit/c8854d2ca4be9ee6946e6d17b0e17d9ef130ee81
215
+ if RUBY_VERSION < "2.3" || backed_up_method_owner[method_name.to_sym] == self
216
+ alias_method method_name, alias_method_name
217
+ end
218
+ remove_method alias_method_name
219
+ end
220
+ end
221
+
222
+ def remove_dummy_method!(method_name)
223
+ @klass.class_exec do
224
+ remove_method method_name
225
+ end
226
+ end
227
+
228
+ def backup_method!(method_name)
229
+ return unless public_protected_or_private_method_defined?(method_name)
230
+
231
+ alias_method_name = build_alias_method_name(method_name)
232
+ @backed_up_method_owner[method_name.to_sym] ||= @klass.instance_method(method_name).owner
233
+ @klass.class_exec do
234
+ alias_method alias_method_name, method_name
235
+ end
236
+ end
237
+
238
+ def public_protected_or_private_method_defined?(method_name)
239
+ MethodReference.method_defined_at_any_visibility?(@klass, method_name)
240
+ end
241
+
242
+ def observe!(method_name)
243
+ allow_no_prepended_module_definition_of(method_name)
244
+
245
+ if RSpec::Mocks.configuration.verify_partial_doubles? && !Mocks.configuration.temporarily_suppress_partial_double_verification
246
+ unless public_protected_or_private_method_defined?(method_name)
247
+ AnyInstance.error_generator.raise_does_not_implement_error(@klass, method_name)
248
+ end
249
+ end
250
+
251
+ stop_observing!(method_name) if already_observing?(method_name)
252
+ @observed_methods << method_name
253
+ backup_method!(method_name)
254
+ recorder = self
255
+ @klass.__send__(:define_method, method_name) do |*args, &blk|
256
+ recorder.playback!(self, method_name)
257
+ __send__(method_name, *args, &blk)
258
+ end
259
+ end
260
+
261
+ def mark_invoked!(method_name)
262
+ backup_method!(method_name)
263
+ recorder = self
264
+ @klass.__send__(:define_method, method_name) do |*_args, &_blk|
265
+ invoked_instance = recorder.instance_that_received(method_name)
266
+ inspect = "#<#{self.class}:#{object_id} #{instance_variables.map { |name| "#{name}=#{instance_variable_get name}" }.join(', ')}>"
267
+ AnyInstance.error_generator.raise_message_already_received_by_other_instance_error(
268
+ method_name, inspect, invoked_instance
269
+ )
270
+ end
271
+ end
272
+
273
+ if Support::RubyFeatures.module_prepends_supported?
274
+ def allow_no_prepended_module_definition_of(method_name)
275
+ prepended_modules = RSpec::Mocks::Proxy.prepended_modules_of(@klass)
276
+ problem_mod = prepended_modules.find { |mod| mod.method_defined?(method_name) }
277
+ return unless problem_mod
278
+
279
+ AnyInstance.error_generator.raise_not_supported_with_prepend_error(method_name, problem_mod)
280
+ end
281
+ else
282
+ def allow_no_prepended_module_definition_of(_method_name)
283
+ # nothing to do; prepends aren't supported on this version of ruby
284
+ end
285
+ end
286
+ end
287
+ end
288
+ end
289
+ end
@@ -0,0 +1,51 @@
1
+ module RSpec
2
+ module Mocks
3
+ module AnyInstance
4
+ # @private
5
+ class StubChain < Chain
6
+ # @private
7
+ def expectation_fulfilled?
8
+ true
9
+ end
10
+
11
+ private
12
+
13
+ def create_message_expectation_on(instance)
14
+ proxy = ::RSpec::Mocks.space.proxy_for(instance)
15
+ method_name, opts = @expectation_args
16
+ opts = (opts || {}).merge(:expected_form => IGNORED_BACKTRACE_LINE)
17
+
18
+ stub = proxy.add_stub(method_name, opts, &@expectation_block)
19
+ @recorder.stubs[stub.message] << stub
20
+
21
+ if RSpec::Mocks.configuration.yield_receiver_to_any_instance_implementation_blocks?
22
+ stub.and_yield_receiver_to_implementation
23
+ end
24
+
25
+ stub
26
+ end
27
+
28
+ InvocationOrder =
29
+ {
30
+ :and_return => [:with, nil],
31
+ :and_raise => [:with, nil],
32
+ :and_yield => [:with, :and_yield, nil],
33
+ :and_throw => [:with, nil],
34
+ :and_call_original => [:with, nil],
35
+ :and_wrap_original => [:with, nil]
36
+ }.freeze
37
+
38
+ EmptyInvocationOrder = {}.freeze
39
+
40
+ def invocation_order
41
+ InvocationOrder
42
+ end
43
+
44
+ def verify_invocation_order(rspec_method_name, *_args, &_block)
45
+ return if invocation_order.fetch(rspec_method_name, [nil]).include?(last_message)
46
+ raise NoMethodError, "Undefined method #{rspec_method_name}"
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end