rspec-mocks-diag 3.8.1.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 (48) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.yardopts +6 -0
  4. data/Changelog.md +1108 -0
  5. data/LICENSE.md +25 -0
  6. data/README.md +460 -0
  7. data/lib/rspec/mocks.rb +130 -0
  8. data/lib/rspec/mocks/any_instance.rb +11 -0
  9. data/lib/rspec/mocks/any_instance/chain.rb +110 -0
  10. data/lib/rspec/mocks/any_instance/error_generator.rb +31 -0
  11. data/lib/rspec/mocks/any_instance/expect_chain_chain.rb +31 -0
  12. data/lib/rspec/mocks/any_instance/expectation_chain.rb +50 -0
  13. data/lib/rspec/mocks/any_instance/message_chains.rb +83 -0
  14. data/lib/rspec/mocks/any_instance/proxy.rb +116 -0
  15. data/lib/rspec/mocks/any_instance/recorder.rb +289 -0
  16. data/lib/rspec/mocks/any_instance/stub_chain.rb +51 -0
  17. data/lib/rspec/mocks/any_instance/stub_chain_chain.rb +23 -0
  18. data/lib/rspec/mocks/argument_list_matcher.rb +100 -0
  19. data/lib/rspec/mocks/argument_matchers.rb +320 -0
  20. data/lib/rspec/mocks/configuration.rb +212 -0
  21. data/lib/rspec/mocks/error_generator.rb +378 -0
  22. data/lib/rspec/mocks/example_methods.rb +434 -0
  23. data/lib/rspec/mocks/instance_method_stasher.rb +146 -0
  24. data/lib/rspec/mocks/marshal_extension.rb +41 -0
  25. data/lib/rspec/mocks/matchers/expectation_customization.rb +20 -0
  26. data/lib/rspec/mocks/matchers/have_received.rb +134 -0
  27. data/lib/rspec/mocks/matchers/receive.rb +132 -0
  28. data/lib/rspec/mocks/matchers/receive_message_chain.rb +82 -0
  29. data/lib/rspec/mocks/matchers/receive_messages.rb +77 -0
  30. data/lib/rspec/mocks/message_chain.rb +87 -0
  31. data/lib/rspec/mocks/message_expectation.rb +748 -0
  32. data/lib/rspec/mocks/method_double.rb +287 -0
  33. data/lib/rspec/mocks/method_reference.rb +202 -0
  34. data/lib/rspec/mocks/minitest_integration.rb +68 -0
  35. data/lib/rspec/mocks/mutate_const.rb +339 -0
  36. data/lib/rspec/mocks/object_reference.rb +149 -0
  37. data/lib/rspec/mocks/order_group.rb +81 -0
  38. data/lib/rspec/mocks/proxy.rb +485 -0
  39. data/lib/rspec/mocks/space.rb +238 -0
  40. data/lib/rspec/mocks/standalone.rb +3 -0
  41. data/lib/rspec/mocks/syntax.rb +325 -0
  42. data/lib/rspec/mocks/targets.rb +124 -0
  43. data/lib/rspec/mocks/test_double.rb +171 -0
  44. data/lib/rspec/mocks/verifying_double.rb +129 -0
  45. data/lib/rspec/mocks/verifying_message_expectation.rb +54 -0
  46. data/lib/rspec/mocks/verifying_proxy.rb +220 -0
  47. data/lib/rspec/mocks/version.rb +9 -0
  48. metadata +186 -0
@@ -0,0 +1,82 @@
1
+ RSpec::Support.require_rspec_mocks 'matchers/expectation_customization'
2
+
3
+ module RSpec
4
+ module Mocks
5
+ module Matchers
6
+ # @private
7
+ class ReceiveMessageChain
8
+ include Matcher
9
+
10
+ def initialize(chain, &block)
11
+ @chain = chain
12
+ @block = block
13
+ @recorded_customizations = []
14
+ end
15
+
16
+ [:with, :and_return, :and_throw, :and_raise, :and_yield, :and_call_original].each do |msg|
17
+ define_method(msg) do |*args, &block|
18
+ @recorded_customizations << ExpectationCustomization.new(msg, args, block)
19
+ self
20
+ end
21
+ end
22
+
23
+ def name
24
+ "receive_message_chain"
25
+ end
26
+
27
+ def description
28
+ "receive message chain #{formatted_chain}"
29
+ end
30
+
31
+ def setup_allowance(subject, &block)
32
+ chain = StubChain.stub_chain_on(subject, *@chain, &(@block || block))
33
+ replay_customizations(chain)
34
+ end
35
+
36
+ def setup_any_instance_allowance(subject, &block)
37
+ proxy = ::RSpec::Mocks.space.any_instance_proxy_for(subject)
38
+ chain = proxy.stub_chain(*@chain, &(@block || block))
39
+ replay_customizations(chain)
40
+ end
41
+
42
+ def setup_any_instance_expectation(subject, &block)
43
+ proxy = ::RSpec::Mocks.space.any_instance_proxy_for(subject)
44
+ chain = proxy.expect_chain(*@chain, &(@block || block))
45
+ replay_customizations(chain)
46
+ end
47
+
48
+ def setup_expectation(subject, &block)
49
+ chain = ExpectChain.expect_chain_on(subject, *@chain, &(@block || block))
50
+ replay_customizations(chain)
51
+ end
52
+
53
+ def setup_negative_expectation(*_args)
54
+ raise NegationUnsupportedError,
55
+ "`expect(...).not_to receive_message_chain` is not supported " \
56
+ "since it doesn't really make sense. What would it even mean?"
57
+ end
58
+
59
+ alias matches? setup_expectation
60
+ alias does_not_match? setup_negative_expectation
61
+
62
+ private
63
+
64
+ def replay_customizations(chain)
65
+ @recorded_customizations.each do |customization|
66
+ customization.playback_onto(chain)
67
+ end
68
+ end
69
+
70
+ def formatted_chain
71
+ @formatted_chain ||= @chain.map do |part|
72
+ if Hash === part
73
+ part.keys.first.to_s
74
+ else
75
+ part.to_s
76
+ end
77
+ end.join(".")
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,77 @@
1
+ module RSpec
2
+ module Mocks
3
+ module Matchers
4
+ # @private
5
+ class ReceiveMessages
6
+ include Matcher
7
+
8
+ def initialize(message_return_value_hash)
9
+ @message_return_value_hash = message_return_value_hash
10
+ @backtrace_line = CallerFilter.first_non_rspec_line
11
+ end
12
+
13
+ def name
14
+ "receive_messages"
15
+ end
16
+
17
+ def description
18
+ "receive messages: #{@message_return_value_hash.inspect}"
19
+ end
20
+
21
+ def setup_expectation(subject)
22
+ warn_about_block if block_given?
23
+ each_message_on(proxy_on(subject)) do |host, message, return_value|
24
+ host.add_simple_expectation(message, return_value, @backtrace_line)
25
+ end
26
+ end
27
+ alias matches? setup_expectation
28
+
29
+ def setup_negative_expectation(_subject)
30
+ raise NegationUnsupportedError,
31
+ "`expect(...).to_not receive_messages` is not supported since it " \
32
+ "doesn't really make sense. What would it even mean?"
33
+ end
34
+ alias does_not_match? setup_negative_expectation
35
+
36
+ def setup_allowance(subject)
37
+ warn_about_block if block_given?
38
+ each_message_on(proxy_on(subject)) do |host, message, return_value|
39
+ host.add_simple_stub(message, return_value)
40
+ end
41
+ end
42
+
43
+ def setup_any_instance_expectation(subject)
44
+ warn_about_block if block_given?
45
+ each_message_on(any_instance_of(subject)) do |host, message, return_value|
46
+ host.should_receive(message).and_return(return_value)
47
+ end
48
+ end
49
+
50
+ def setup_any_instance_allowance(subject)
51
+ warn_about_block if block_given?
52
+ any_instance_of(subject).stub(@message_return_value_hash)
53
+ end
54
+
55
+ def warn_about_block
56
+ raise "Implementation blocks aren't supported with `receive_messages`"
57
+ end
58
+
59
+ private
60
+
61
+ def proxy_on(subject)
62
+ ::RSpec::Mocks.space.proxy_for(subject)
63
+ end
64
+
65
+ def any_instance_of(subject)
66
+ ::RSpec::Mocks.space.any_instance_proxy_for(subject)
67
+ end
68
+
69
+ def each_message_on(host)
70
+ @message_return_value_hash.each do |message, value|
71
+ yield host, message, value
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,87 @@
1
+ module RSpec
2
+ module Mocks
3
+ # @private
4
+ class MessageChain
5
+ attr_reader :object, :chain, :block
6
+
7
+ def initialize(object, *chain, &blk)
8
+ @object = object
9
+ @chain, @block = format_chain(*chain, &blk)
10
+ end
11
+
12
+ # @api private
13
+ def setup_chain
14
+ if chain.length > 1
15
+ if (matching_stub = find_matching_stub)
16
+ chain.shift
17
+ chain_on(matching_stub.invoke(nil), *chain, &@block)
18
+ elsif (matching_expectation = find_matching_expectation)
19
+ chain.shift
20
+ chain_on(matching_expectation.invoke_without_incrementing_received_count(nil), *chain, &@block)
21
+ else
22
+ next_in_chain = Double.new
23
+ expectation(object, chain.shift) { next_in_chain }
24
+ chain_on(next_in_chain, *chain, &@block)
25
+ end
26
+ else
27
+ expectation(object, chain.shift, &@block)
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def chain_on(object, *chain, &block)
34
+ initialize(object, *chain, &block)
35
+ setup_chain
36
+ end
37
+
38
+ def format_chain(*chain, &blk)
39
+ if Hash === chain.last
40
+ hash = chain.pop
41
+ hash.each do |k, v|
42
+ chain << k
43
+ blk = Proc.new { v }
44
+ end
45
+ end
46
+ return chain.join('.').split('.'), blk
47
+ end
48
+
49
+ def find_matching_stub
50
+ ::RSpec::Mocks.space.proxy_for(object).
51
+ __send__(:find_matching_method_stub, chain.first.to_sym)
52
+ end
53
+
54
+ def find_matching_expectation
55
+ ::RSpec::Mocks.space.proxy_for(object).
56
+ __send__(:find_matching_expectation, chain.first.to_sym)
57
+ end
58
+ end
59
+
60
+ # @private
61
+ class ExpectChain < MessageChain
62
+ # @api private
63
+ def self.expect_chain_on(object, *chain, &blk)
64
+ new(object, *chain, &blk).setup_chain
65
+ end
66
+
67
+ private
68
+
69
+ def expectation(object, message, &return_block)
70
+ ::RSpec::Mocks.expect_message(object, message, {}, &return_block)
71
+ end
72
+ end
73
+
74
+ # @private
75
+ class StubChain < MessageChain
76
+ def self.stub_chain_on(object, *chain, &blk)
77
+ new(object, *chain, &blk).setup_chain
78
+ end
79
+
80
+ private
81
+
82
+ def expectation(object, message, &return_block)
83
+ ::RSpec::Mocks.allow_message(object, message, {}, &return_block)
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,748 @@
1
+ module RSpec
2
+ module Mocks
3
+ # A message expectation that only allows concrete return values to be set
4
+ # for a message. While this same effect can be achieved using a standard
5
+ # MessageExpectation, this version is much faster and so can be used as an
6
+ # optimization.
7
+ #
8
+ # @private
9
+ class SimpleMessageExpectation
10
+ def initialize(message, response, error_generator, backtrace_line=nil)
11
+ @message, @response, @error_generator, @backtrace_line = message.to_sym, response, error_generator, backtrace_line
12
+ @received = false
13
+ end
14
+
15
+ def invoke(*_)
16
+ @received = true
17
+ @response
18
+ end
19
+
20
+ def matches?(message, *_)
21
+ @message == message.to_sym
22
+ end
23
+
24
+ def called_max_times?
25
+ false
26
+ end
27
+
28
+ def verify_messages_received
29
+ return if @received
30
+ @error_generator.raise_expectation_error(
31
+ @message, 1, ArgumentListMatcher::MATCH_ALL, 0, nil, [], @backtrace_line
32
+ )
33
+ end
34
+
35
+ def unadvise(_)
36
+ end
37
+ end
38
+
39
+ # Represents an individual method stub or message expectation. The methods
40
+ # defined here can be used to configure how it behaves. The methods return
41
+ # `self` so that they can be chained together to form a fluent interface.
42
+ class MessageExpectation
43
+ # @!group Configuring Responses
44
+
45
+ # @overload and_return(value)
46
+ # @overload and_return(first_value, second_value)
47
+ #
48
+ # Tells the object to return a value when it receives the message. Given
49
+ # more than one value, the first value is returned the first time the
50
+ # message is received, the second value is returned the next time, etc,
51
+ # etc.
52
+ #
53
+ # If the message is received more times than there are values, the last
54
+ # value is received for every subsequent call.
55
+ #
56
+ # @return [nil] No further chaining is supported after this.
57
+ # @example
58
+ # allow(counter).to receive(:count).and_return(1)
59
+ # counter.count # => 1
60
+ # counter.count # => 1
61
+ #
62
+ # allow(counter).to receive(:count).and_return(1,2,3)
63
+ # counter.count # => 1
64
+ # counter.count # => 2
65
+ # counter.count # => 3
66
+ # counter.count # => 3
67
+ # counter.count # => 3
68
+ # # etc
69
+ def and_return(first_value, *values)
70
+ raise_already_invoked_error_if_necessary(__method__)
71
+ if negative?
72
+ raise "`and_return` is not supported with negative message expectations"
73
+ end
74
+
75
+ if block_given?
76
+ raise ArgumentError, "Implementation blocks aren't supported with `and_return`"
77
+ end
78
+
79
+ values.unshift(first_value)
80
+ @expected_received_count = [@expected_received_count, values.size].max unless ignoring_args? || (@expected_received_count == 0 && @at_least)
81
+ self.terminal_implementation_action = AndReturnImplementation.new(values)
82
+
83
+ nil
84
+ end
85
+
86
+ # Tells the object to delegate to the original unmodified method
87
+ # when it receives the message.
88
+ #
89
+ # @note This is only available on partial doubles.
90
+ #
91
+ # @return [nil] No further chaining is supported after this.
92
+ # @example
93
+ # expect(counter).to receive(:increment).and_call_original
94
+ # original_count = counter.count
95
+ # counter.increment
96
+ # expect(counter.count).to eq(original_count + 1)
97
+ def and_call_original
98
+ wrap_original(__method__) do |original, *args, &block|
99
+ original.call(*args, &block)
100
+ end
101
+ end
102
+
103
+ # Decorates the stubbed method with the supplied block. The original
104
+ # unmodified method is passed to the block along with any method call
105
+ # arguments so you can delegate to it, whilst still being able to
106
+ # change what args are passed to it and/or change the return value.
107
+ #
108
+ # @note This is only available on partial doubles.
109
+ #
110
+ # @return [nil] No further chaining is supported after this.
111
+ # @example
112
+ # expect(api).to receive(:large_list).and_wrap_original do |original_method, *args, &block|
113
+ # original_method.call(*args, &block).first(10)
114
+ # end
115
+ def and_wrap_original(&block)
116
+ wrap_original(__method__, &block)
117
+ end
118
+
119
+ # @overload and_raise
120
+ # @overload and_raise(ExceptionClass)
121
+ # @overload and_raise(ExceptionClass, message)
122
+ # @overload and_raise(exception_instance)
123
+ #
124
+ # Tells the object to raise an exception when the message is received.
125
+ #
126
+ # @return [nil] No further chaining is supported after this.
127
+ # @note
128
+ # When you pass an exception class, the MessageExpectation will raise
129
+ # an instance of it, creating it with `exception` and passing `message`
130
+ # if specified. If the exception class initializer requires more than
131
+ # one parameters, you must pass in an instance and not the class,
132
+ # otherwise this method will raise an ArgumentError exception.
133
+ #
134
+ # @example
135
+ # allow(car).to receive(:go).and_raise
136
+ # allow(car).to receive(:go).and_raise(OutOfGas)
137
+ # allow(car).to receive(:go).and_raise(OutOfGas, "At least 2 oz of gas needed to drive")
138
+ # allow(car).to receive(:go).and_raise(OutOfGas.new(2, :oz))
139
+ def and_raise(*args)
140
+ raise_already_invoked_error_if_necessary(__method__)
141
+ self.terminal_implementation_action = Proc.new { raise(*args) }
142
+ nil
143
+ end
144
+
145
+ # @overload and_throw(symbol)
146
+ # @overload and_throw(symbol, object)
147
+ #
148
+ # Tells the object to throw a symbol (with the object if that form is
149
+ # used) when the message is received.
150
+ #
151
+ # @return [nil] No further chaining is supported after this.
152
+ # @example
153
+ # allow(car).to receive(:go).and_throw(:out_of_gas)
154
+ # allow(car).to receive(:go).and_throw(:out_of_gas, :level => 0.1)
155
+ def and_throw(*args)
156
+ raise_already_invoked_error_if_necessary(__method__)
157
+ self.terminal_implementation_action = Proc.new { throw(*args) }
158
+ nil
159
+ end
160
+
161
+ # Tells the object to yield one or more args to a block when the message
162
+ # is received.
163
+ #
164
+ # @return [MessageExpectation] self, to support further chaining.
165
+ # @example
166
+ # stream.stub(:open).and_yield(StringIO.new)
167
+ def and_yield(*args, &block)
168
+ raise_already_invoked_error_if_necessary(__method__)
169
+ yield @eval_context = Object.new if block
170
+
171
+ # Initialize args to yield now that it's being used, see also: comment
172
+ # in constructor.
173
+ @args_to_yield ||= []
174
+
175
+ @args_to_yield << args
176
+ self.initial_implementation_action = AndYieldImplementation.new(@args_to_yield, @eval_context, @error_generator)
177
+ self
178
+ end
179
+ # @!endgroup
180
+
181
+ # @!group Constraining Receive Counts
182
+
183
+ # Constrain a message expectation to be received a specific number of
184
+ # times.
185
+ #
186
+ # @return [MessageExpectation] self, to support further chaining.
187
+ # @example
188
+ # expect(dealer).to receive(:deal_card).exactly(10).times
189
+ def exactly(n, &block)
190
+ raise_already_invoked_error_if_necessary(__method__)
191
+ self.inner_implementation_action = block
192
+ set_expected_received_count :exactly, n
193
+ self
194
+ end
195
+
196
+ # Constrain a message expectation to be received at least a specific
197
+ # number of times.
198
+ #
199
+ # @return [MessageExpectation] self, to support further chaining.
200
+ # @example
201
+ # expect(dealer).to receive(:deal_card).at_least(9).times
202
+ def at_least(n, &block)
203
+ raise_already_invoked_error_if_necessary(__method__)
204
+ set_expected_received_count :at_least, n
205
+
206
+ if n == 0
207
+ raise "at_least(0) has been removed, use allow(...).to receive(:message) instead"
208
+ end
209
+
210
+ self.inner_implementation_action = block
211
+
212
+ self
213
+ end
214
+
215
+ # Constrain a message expectation to be received at most a specific
216
+ # number of times.
217
+ #
218
+ # @return [MessageExpectation] self, to support further chaining.
219
+ # @example
220
+ # expect(dealer).to receive(:deal_card).at_most(10).times
221
+ def at_most(n, &block)
222
+ raise_already_invoked_error_if_necessary(__method__)
223
+ self.inner_implementation_action = block
224
+ set_expected_received_count :at_most, n
225
+ self
226
+ end
227
+
228
+ # Syntactic sugar for `exactly`, `at_least` and `at_most`
229
+ #
230
+ # @return [MessageExpectation] self, to support further chaining.
231
+ # @example
232
+ # expect(dealer).to receive(:deal_card).exactly(10).times
233
+ # expect(dealer).to receive(:deal_card).at_least(10).times
234
+ # expect(dealer).to receive(:deal_card).at_most(10).times
235
+ def times(&block)
236
+ self.inner_implementation_action = block
237
+ self
238
+ end
239
+
240
+ # Expect a message not to be received at all.
241
+ #
242
+ # @return [MessageExpectation] self, to support further chaining.
243
+ # @example
244
+ # expect(car).to receive(:stop).never
245
+ def never
246
+ error_generator.raise_double_negation_error("expect(obj)") if negative?
247
+ @expected_received_count = 0
248
+ self
249
+ end
250
+
251
+ # Expect a message to be received exactly one time.
252
+ #
253
+ # @return [MessageExpectation] self, to support further chaining.
254
+ # @example
255
+ # expect(car).to receive(:go).once
256
+ def once(&block)
257
+ self.inner_implementation_action = block
258
+ set_expected_received_count :exactly, 1
259
+ self
260
+ end
261
+
262
+ # Expect a message to be received exactly two times.
263
+ #
264
+ # @return [MessageExpectation] self, to support further chaining.
265
+ # @example
266
+ # expect(car).to receive(:go).twice
267
+ def twice(&block)
268
+ self.inner_implementation_action = block
269
+ set_expected_received_count :exactly, 2
270
+ self
271
+ end
272
+
273
+ # Expect a message to be received exactly three times.
274
+ #
275
+ # @return [MessageExpectation] self, to support further chaining.
276
+ # @example
277
+ # expect(car).to receive(:go).thrice
278
+ def thrice(&block)
279
+ self.inner_implementation_action = block
280
+ set_expected_received_count :exactly, 3
281
+ self
282
+ end
283
+ # @!endgroup
284
+
285
+ # @!group Other Constraints
286
+
287
+ # Constrains a stub or message expectation to invocations with specific
288
+ # arguments.
289
+ #
290
+ # With a stub, if the message might be received with other args as well,
291
+ # you should stub a default value first, and then stub or mock the same
292
+ # message using `with` to constrain to specific arguments.
293
+ #
294
+ # A message expectation will fail if the message is received with different
295
+ # arguments.
296
+ #
297
+ # @return [MessageExpectation] self, to support further chaining.
298
+ # @example
299
+ # allow(cart).to receive(:add) { :failure }
300
+ # allow(cart).to receive(:add).with(Book.new(:isbn => 1934356379)) { :success }
301
+ # cart.add(Book.new(:isbn => 1234567890))
302
+ # # => :failure
303
+ # cart.add(Book.new(:isbn => 1934356379))
304
+ # # => :success
305
+ #
306
+ # expect(cart).to receive(:add).with(Book.new(:isbn => 1934356379)) { :success }
307
+ # cart.add(Book.new(:isbn => 1234567890))
308
+ # # => failed expectation
309
+ # cart.add(Book.new(:isbn => 1934356379))
310
+ # # => passes
311
+ def with(*args, &block)
312
+ raise_already_invoked_error_if_necessary(__method__)
313
+ if args.empty?
314
+ raise ArgumentError,
315
+ "`with` must have at least one argument. Use `no_args` matcher to set the expectation of receiving no arguments."
316
+ end
317
+
318
+ self.inner_implementation_action = block
319
+ @argument_list_matcher = ArgumentListMatcher.new(*args)
320
+ self
321
+ end
322
+
323
+ # Expect messages to be received in a specific order.
324
+ #
325
+ # @return [MessageExpectation] self, to support further chaining.
326
+ # @example
327
+ # expect(api).to receive(:prepare).ordered
328
+ # expect(api).to receive(:run).ordered
329
+ # expect(api).to receive(:finish).ordered
330
+ def ordered(&block)
331
+ if type == :stub
332
+ RSpec.warning(
333
+ "`allow(...).to receive(..).ordered` is not supported and will " \
334
+ "have no effect, use `and_return(*ordered_values)` instead."
335
+ )
336
+ end
337
+
338
+ self.inner_implementation_action = block
339
+ additional_expected_calls.times do
340
+ @order_group.register(self)
341
+ end
342
+ @ordered = true
343
+ self
344
+ end
345
+
346
+ # @return [String] a nice representation of the message expectation
347
+ def to_s
348
+ args_description = error_generator.method_call_args_description(@argument_list_matcher.expected_args, "", "") { true }
349
+ args_description = "(#{args_description})" unless args_description.start_with?("(")
350
+ "#<#{self.class} #{error_generator.intro}.#{message}#{args_description}>"
351
+ end
352
+ alias inspect to_s
353
+
354
+ # @private
355
+ # Contains the parts of `MessageExpectation` that aren't part of
356
+ # rspec-mocks' public API. The class is very big and could really use
357
+ # some collaborators it delegates to for this stuff but for now this was
358
+ # the simplest way to split the public from private stuff to make it
359
+ # easier to publish the docs for the APIs we want published.
360
+ module ImplementationDetails
361
+ attr_accessor :error_generator, :implementation
362
+ attr_reader :message
363
+ attr_reader :orig_object
364
+ attr_writer :expected_received_count, :expected_from, :argument_list_matcher
365
+ protected :expected_received_count=, :expected_from=, :error_generator, :error_generator=, :implementation=
366
+
367
+ # @private
368
+ attr_reader :type
369
+
370
+ # rubocop:disable Metrics/ParameterLists
371
+ def initialize(error_generator, expectation_ordering, expected_from, method_double,
372
+ type=:expectation, opts={}, &implementation_block)
373
+ @type = type
374
+ @error_generator = error_generator
375
+ @error_generator.opts = opts
376
+ @expected_from = expected_from
377
+ @method_double = method_double
378
+ @orig_object = @method_double.object
379
+ @message = @method_double.method_name
380
+ @actual_received_count = 0
381
+ @expected_received_count = type == :expectation ? 1 : :any
382
+ @argument_list_matcher = ArgumentListMatcher::MATCH_ALL
383
+ @order_group = expectation_ordering
384
+ @order_group.register(self) unless type == :stub
385
+ @expectation_type = type
386
+ @ordered = false
387
+ @at_least = @at_most = @exactly = nil
388
+
389
+ # Initialized to nil so that we don't allocate an array for every
390
+ # mock or stub. See also comment in `and_yield`.
391
+ @args_to_yield = nil
392
+ @eval_context = nil
393
+ @yield_receiver_to_implementation_block = false
394
+
395
+ @implementation = Implementation.new
396
+ self.inner_implementation_action = implementation_block
397
+ end
398
+ # rubocop:enable Metrics/ParameterLists
399
+
400
+ def expected_args
401
+ @argument_list_matcher.expected_args
402
+ end
403
+
404
+ def and_yield_receiver_to_implementation
405
+ @yield_receiver_to_implementation_block = true
406
+ self
407
+ end
408
+
409
+ def yield_receiver_to_implementation_block?
410
+ @yield_receiver_to_implementation_block
411
+ end
412
+
413
+ def matches?(message, *args)
414
+ @message == message && @argument_list_matcher.args_match?(*args)
415
+ end
416
+
417
+ def safe_invoke(parent_stub, *args, &block)
418
+ invoke_incrementing_actual_calls_by(1, false, parent_stub, *args, &block)
419
+ end
420
+
421
+ def invoke(parent_stub, *args, &block)
422
+ @args_history ||= []
423
+ begin
424
+ fail
425
+ rescue => e
426
+ backtrace = e.backtrace
427
+ end
428
+ @args_history << [args, backtrace]
429
+ invoke_incrementing_actual_calls_by(1, true, parent_stub, *args, &block)
430
+ end
431
+
432
+ def invoke_without_incrementing_received_count(parent_stub, *args, &block)
433
+ invoke_incrementing_actual_calls_by(0, true, parent_stub, *args, &block)
434
+ end
435
+
436
+ def negative?
437
+ @expected_received_count == 0 && !@at_least
438
+ end
439
+
440
+ def called_max_times?
441
+ @expected_received_count != :any &&
442
+ !@at_least &&
443
+ @expected_received_count > 0 &&
444
+ @actual_received_count >= @expected_received_count
445
+ end
446
+
447
+ def matches_name_but_not_args(message, *args)
448
+ @message == message && !@argument_list_matcher.args_match?(*args)
449
+ end
450
+
451
+ def verify_messages_received
452
+ return if expected_messages_received?
453
+ generate_error
454
+ end
455
+
456
+ def expected_messages_received?
457
+ ignoring_args? || matches_exact_count? || matches_at_least_count? || matches_at_most_count?
458
+ end
459
+
460
+ def ensure_expected_ordering_received!
461
+ @order_group.verify_invocation_order(self) if @ordered
462
+ true
463
+ end
464
+
465
+ def ignoring_args?
466
+ @expected_received_count == :any
467
+ end
468
+
469
+ def matches_at_least_count?
470
+ @at_least && @actual_received_count >= @expected_received_count
471
+ end
472
+
473
+ def matches_at_most_count?
474
+ @at_most && @actual_received_count <= @expected_received_count
475
+ end
476
+
477
+ def matches_exact_count?
478
+ @expected_received_count == @actual_received_count
479
+ end
480
+
481
+ def similar_messages
482
+ @similar_messages ||= []
483
+ end
484
+
485
+ def advise(*args)
486
+ similar_messages << args
487
+ end
488
+
489
+ def unadvise(args)
490
+ similar_messages.delete_if { |message| args.include?(message) }
491
+ end
492
+
493
+ def generate_error
494
+ if similar_messages.empty?
495
+ @error_generator.raise_expectation_error(
496
+ @message, @expected_received_count, @argument_list_matcher,
497
+ @actual_received_count, expectation_count_type, expected_args,
498
+ @expected_from, exception_source_id, @args_history
499
+ )
500
+ else
501
+ @error_generator.raise_similar_message_args_error(
502
+ self, @similar_messages, @expected_from
503
+ )
504
+ end
505
+ end
506
+
507
+ def raise_unexpected_message_args_error(args_for_multiple_calls)
508
+ @error_generator.raise_unexpected_message_args_error(self, args_for_multiple_calls, exception_source_id)
509
+ end
510
+
511
+ def expectation_count_type
512
+ return :at_least if @at_least
513
+ return :at_most if @at_most
514
+ nil
515
+ end
516
+
517
+ def description_for(verb)
518
+ @error_generator.describe_expectation(
519
+ verb, @message, @expected_received_count,
520
+ @actual_received_count, expected_args
521
+ )
522
+ end
523
+
524
+ def raise_out_of_order_error
525
+ @error_generator.raise_out_of_order_error @message
526
+ end
527
+
528
+ def additional_expected_calls
529
+ return 0 if @expectation_type == :stub || !@exactly
530
+ @expected_received_count - 1
531
+ end
532
+
533
+ def ordered?
534
+ @ordered
535
+ end
536
+
537
+ def negative_expectation_for?(message)
538
+ @message == message && negative?
539
+ end
540
+
541
+ def actual_received_count_matters?
542
+ @at_least || @at_most || @exactly
543
+ end
544
+
545
+ def increase_actual_received_count!
546
+ @actual_received_count += 1
547
+ end
548
+
549
+ private
550
+
551
+ def exception_source_id
552
+ @exception_source_id ||= "#{self.class.name} #{__id__}"
553
+ end
554
+
555
+ def invoke_incrementing_actual_calls_by(increment, allowed_to_fail, parent_stub, *args, &block)
556
+ args.unshift(orig_object) if yield_receiver_to_implementation_block?
557
+
558
+ if negative? || (allowed_to_fail && (@exactly || @at_most) && (@actual_received_count == @expected_received_count))
559
+ # args are the args we actually received, @argument_list_matcher is the
560
+ # list of args we were expecting
561
+ @error_generator.raise_expectation_error(
562
+ @message, @expected_received_count,
563
+ @argument_list_matcher,
564
+ @actual_received_count + increment,
565
+ expectation_count_type, args, nil, exception_source_id
566
+ )
567
+ end
568
+
569
+ @order_group.handle_order_constraint self
570
+
571
+ if implementation.present?
572
+ implementation.call(*args, &block)
573
+ elsif parent_stub
574
+ parent_stub.invoke(nil, *args, &block)
575
+ end
576
+ ensure
577
+ @actual_received_count += increment
578
+ end
579
+
580
+ def has_been_invoked?
581
+ @actual_received_count > 0
582
+ end
583
+
584
+ def raise_already_invoked_error_if_necessary(calling_customization)
585
+ return unless has_been_invoked?
586
+
587
+ error_generator.raise_already_invoked_error(message, calling_customization)
588
+ end
589
+
590
+ def set_expected_received_count(relativity, n)
591
+ raise "`count` is not supported with negative message expectations" if negative?
592
+ @at_least = (relativity == :at_least)
593
+ @at_most = (relativity == :at_most)
594
+ @exactly = (relativity == :exactly)
595
+ @expected_received_count = case n
596
+ when Numeric then n
597
+ when :once then 1
598
+ when :twice then 2
599
+ when :thrice then 3
600
+ end
601
+ end
602
+
603
+ def initial_implementation_action=(action)
604
+ implementation.initial_action = action
605
+ end
606
+
607
+ def inner_implementation_action=(action)
608
+ return unless action
609
+ warn_about_stub_override if implementation.inner_action
610
+ implementation.inner_action = action
611
+ end
612
+
613
+ def terminal_implementation_action=(action)
614
+ implementation.terminal_action = action
615
+ end
616
+
617
+ def warn_about_stub_override
618
+ RSpec.warning(
619
+ "You're overriding a previous stub implementation of `#{@message}`. " \
620
+ "Called from #{CallerFilter.first_non_rspec_line}."
621
+ )
622
+ end
623
+
624
+ def wrap_original(method_name, &block)
625
+ if RSpec::Mocks::TestDouble === @method_double.object
626
+ @error_generator.raise_only_valid_on_a_partial_double(method_name)
627
+ else
628
+ warn_about_stub_override if implementation.inner_action
629
+ @implementation = AndWrapOriginalImplementation.new(@method_double.original_implementation_callable, block)
630
+ @yield_receiver_to_implementation_block = false
631
+ end
632
+
633
+ nil
634
+ end
635
+ end
636
+
637
+ include ImplementationDetails
638
+ end
639
+
640
+ # Handles the implementation of an `and_yield` declaration.
641
+ # @private
642
+ class AndYieldImplementation
643
+ def initialize(args_to_yield, eval_context, error_generator)
644
+ @args_to_yield = args_to_yield
645
+ @eval_context = eval_context
646
+ @error_generator = error_generator
647
+ end
648
+
649
+ def call(*_args_to_ignore, &block)
650
+ return if @args_to_yield.empty? && @eval_context.nil?
651
+
652
+ @error_generator.raise_missing_block_error @args_to_yield unless block
653
+ value = nil
654
+ block_signature = Support::BlockSignature.new(block)
655
+
656
+ @args_to_yield.each do |args|
657
+ unless Support::StrictSignatureVerifier.new(block_signature, args).valid?
658
+ @error_generator.raise_wrong_arity_error(args, block_signature)
659
+ end
660
+
661
+ value = @eval_context ? @eval_context.instance_exec(*args, &block) : yield(*args)
662
+ end
663
+ value
664
+ end
665
+ end
666
+
667
+ # Handles the implementation of an `and_return` implementation.
668
+ # @private
669
+ class AndReturnImplementation
670
+ def initialize(values_to_return)
671
+ @values_to_return = values_to_return
672
+ end
673
+
674
+ def call(*_args_to_ignore, &_block)
675
+ if @values_to_return.size > 1
676
+ @values_to_return.shift
677
+ else
678
+ @values_to_return.first
679
+ end
680
+ end
681
+ end
682
+
683
+ # Represents a configured implementation. Takes into account
684
+ # any number of sub-implementations.
685
+ # @private
686
+ class Implementation
687
+ attr_accessor :initial_action, :inner_action, :terminal_action
688
+
689
+ def call(*args, &block)
690
+ actions.map do |action|
691
+ action.call(*args, &block)
692
+ end.last
693
+ end
694
+
695
+ def present?
696
+ actions.any?
697
+ end
698
+
699
+ private
700
+
701
+ def actions
702
+ [initial_action, inner_action, terminal_action].compact
703
+ end
704
+ end
705
+
706
+ # Represents an `and_call_original` implementation.
707
+ # @private
708
+ class AndWrapOriginalImplementation
709
+ def initialize(method, block)
710
+ @method = method
711
+ @block = block
712
+ end
713
+
714
+ CannotModifyFurtherError = Class.new(StandardError)
715
+
716
+ def initial_action=(_value)
717
+ raise cannot_modify_further_error
718
+ end
719
+
720
+ def inner_action=(_value)
721
+ raise cannot_modify_further_error
722
+ end
723
+
724
+ def terminal_action=(_value)
725
+ raise cannot_modify_further_error
726
+ end
727
+
728
+ def present?
729
+ true
730
+ end
731
+
732
+ def inner_action
733
+ true
734
+ end
735
+
736
+ def call(*args, &block)
737
+ @block.call(@method, *args, &block)
738
+ end
739
+
740
+ private
741
+
742
+ def cannot_modify_further_error
743
+ CannotModifyFurtherError.new "This method has already been configured " \
744
+ "to call the original implementation, and cannot be modified further."
745
+ end
746
+ end
747
+ end
748
+ end