rspec-mocks 3.0.4 → 3.12.6

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 (49) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/.document +1 -1
  4. data/.yardopts +1 -1
  5. data/Changelog.md +512 -2
  6. data/{License.txt → LICENSE.md} +5 -4
  7. data/README.md +113 -30
  8. data/lib/rspec/mocks/any_instance/chain.rb +5 -3
  9. data/lib/rspec/mocks/any_instance/error_generator.rb +31 -0
  10. data/lib/rspec/mocks/any_instance/expect_chain_chain.rb +1 -5
  11. data/lib/rspec/mocks/any_instance/expectation_chain.rb +9 -8
  12. data/lib/rspec/mocks/any_instance/message_chains.rb +7 -8
  13. data/lib/rspec/mocks/any_instance/proxy.rb +14 -5
  14. data/lib/rspec/mocks/any_instance/recorder.rb +61 -31
  15. data/lib/rspec/mocks/any_instance/stub_chain.rb +15 -11
  16. data/lib/rspec/mocks/any_instance/stub_chain_chain.rb +1 -5
  17. data/lib/rspec/mocks/any_instance.rb +1 -0
  18. data/lib/rspec/mocks/argument_list_matcher.rb +55 -10
  19. data/lib/rspec/mocks/argument_matchers.rb +88 -30
  20. data/lib/rspec/mocks/configuration.rb +61 -13
  21. data/lib/rspec/mocks/error_generator.rb +250 -107
  22. data/lib/rspec/mocks/example_methods.rb +151 -28
  23. data/lib/rspec/mocks/instance_method_stasher.rb +17 -6
  24. data/lib/rspec/mocks/matchers/have_received.rb +50 -20
  25. data/lib/rspec/mocks/matchers/receive.rb +39 -11
  26. data/lib/rspec/mocks/matchers/receive_message_chain.rb +22 -7
  27. data/lib/rspec/mocks/matchers/receive_messages.rb +12 -7
  28. data/lib/rspec/mocks/message_chain.rb +3 -7
  29. data/lib/rspec/mocks/message_expectation.rb +466 -307
  30. data/lib/rspec/mocks/method_double.rb +88 -29
  31. data/lib/rspec/mocks/method_reference.rb +85 -25
  32. data/lib/rspec/mocks/minitest_integration.rb +68 -0
  33. data/lib/rspec/mocks/mutate_const.rb +50 -109
  34. data/lib/rspec/mocks/object_reference.rb +89 -32
  35. data/lib/rspec/mocks/order_group.rb +4 -5
  36. data/lib/rspec/mocks/proxy.rb +156 -60
  37. data/lib/rspec/mocks/space.rb +52 -35
  38. data/lib/rspec/mocks/standalone.rb +1 -1
  39. data/lib/rspec/mocks/syntax.rb +26 -30
  40. data/lib/rspec/mocks/targets.rb +55 -28
  41. data/lib/rspec/mocks/test_double.rb +43 -7
  42. data/lib/rspec/mocks/verifying_double.rb +27 -33
  43. data/lib/rspec/mocks/{verifying_message_expecation.rb → verifying_message_expectation.rb} +11 -16
  44. data/lib/rspec/mocks/verifying_proxy.rb +77 -26
  45. data/lib/rspec/mocks/version.rb +1 -1
  46. data/lib/rspec/mocks.rb +8 -1
  47. data.tar.gz.sig +0 -0
  48. metadata +80 -43
  49. metadata.gz.sig +0 -0
@@ -1,3 +1,5 @@
1
+ RSpec::Support.require_rspec_support "object_formatter"
2
+
1
3
  module RSpec
2
4
  module Mocks
3
5
  # Raised when a message expectation is not satisfied.
@@ -10,6 +12,19 @@ module RSpec
10
12
  # Raised when doubles or partial doubles are used outside of the per-test lifecycle.
11
13
  OutsideOfExampleError = Class.new(StandardError)
12
14
 
15
+ # Raised when an expectation customization method (e.g. `with`,
16
+ # `and_return`) is called on a message expectation which has already been
17
+ # invoked.
18
+ MockExpectationAlreadyInvokedError = Class.new(Exception)
19
+
20
+ # Raised for situations that RSpec cannot support due to mutations made
21
+ # externally on arguments that RSpec is holding onto to use for later
22
+ # comparisons.
23
+ #
24
+ # @deprecated We no longer raise this error but the constant remains until
25
+ # RSpec 4 for SemVer reasons.
26
+ CannotSupportArgMutationsError = Class.new(StandardError)
27
+
13
28
  # @private
14
29
  UnsupportedMatcherError = Class.new(StandardError)
15
30
  # @private
@@ -21,9 +36,8 @@ module RSpec
21
36
  class ErrorGenerator
22
37
  attr_writer :opts
23
38
 
24
- def initialize(target, name)
39
+ def initialize(target=nil)
25
40
  @target = target
26
- @name = name
27
41
  end
28
42
 
29
43
  # @private
@@ -32,44 +46,65 @@ module RSpec
32
46
  end
33
47
 
34
48
  # @private
35
- def raise_unexpected_message_error(message, *args)
36
- __raise "#{intro} received unexpected message :#{message}#{arg_message(*args)}"
49
+ def raise_unexpected_message_error(message, args)
50
+ __raise "#{intro} received unexpected message :#{message} with #{format_args(args)}"
37
51
  end
38
52
 
39
53
  # @private
40
- def raise_unexpected_message_args_error(expectation, *args)
41
- expected_args = format_args(*expectation.expected_args)
42
- actual_args = format_received_args(*args)
43
- __raise "#{intro} received #{expectation.message.inspect} with unexpected arguments\n expected: #{expected_args}\n got: #{actual_args}"
54
+ def raise_unexpected_message_args_error(expectation, args_for_multiple_calls, source_id=nil)
55
+ __raise error_message(expectation, args_for_multiple_calls), nil, source_id
44
56
  end
45
57
 
46
58
  # @private
47
- def raise_missing_default_stub_error(expectation, *args)
48
- expected_args = format_args(*expectation.expected_args)
49
- actual_args = format_received_args(*args)
50
- __raise "#{intro} received #{expectation.message.inspect} with unexpected arguments\n expected: #{expected_args}\n got: #{actual_args}\n Please stub a default value first if message might be received with other args as well. \n"
59
+ def raise_missing_default_stub_error(expectation, args_for_multiple_calls)
60
+ __raise(
61
+ error_message(expectation, args_for_multiple_calls) +
62
+ "\n Please stub a default value first if message might be received with other args as well. \n"
63
+ )
51
64
  end
52
65
 
53
66
  # @private
54
- def raise_similar_message_args_error(expectation, *args_for_multiple_calls)
55
- expected_args = format_args(*expectation.expected_args)
56
- actual_args = args_for_multiple_calls.collect {|a| format_received_args(*a)}.join(", ")
57
- __raise "#{intro} received #{expectation.message.inspect} with unexpected arguments\n expected: #{expected_args}\n got: #{actual_args}"
67
+ def raise_similar_message_args_error(expectation, args_for_multiple_calls, backtrace_line=nil)
68
+ __raise error_message(expectation, args_for_multiple_calls), backtrace_line
58
69
  end
59
70
 
71
+ def default_error_message(expectation, expected_args, actual_args)
72
+ "#{intro} received #{expectation.message.inspect} #{unexpected_arguments_message(expected_args, actual_args)}".dup
73
+ end
74
+
75
+ # rubocop:disable Metrics/ParameterLists
60
76
  # @private
61
- def raise_expectation_error(message, expected_received_count, argument_list_matcher, actual_received_count, expectation_count_type, *args)
77
+ def raise_expectation_error(message, expected_received_count, argument_list_matcher,
78
+ actual_received_count, expectation_count_type, args,
79
+ backtrace_line=nil, source_id=nil)
62
80
  expected_part = expected_part_of_expectation_error(expected_received_count, expectation_count_type, argument_list_matcher)
63
- received_part = received_part_of_expectation_error(actual_received_count, *args)
64
- __raise "(#{intro}).#{message}#{format_args(*args)}\n #{expected_part}\n #{received_part}"
81
+ received_part = received_part_of_expectation_error(actual_received_count, args)
82
+ __raise "(#{intro(:unwrapped)}).#{message}#{format_args(args)}\n #{expected_part}\n #{received_part}", backtrace_line, source_id
65
83
  end
84
+ # rubocop:enable Metrics/ParameterLists
66
85
 
67
86
  # @private
68
- def raise_unimplemented_error(doubled_module, method_name)
69
- __raise "%s does not implement: %s" % [
70
- doubled_module.description,
71
- method_name
72
- ]
87
+ def raise_unimplemented_error(doubled_module, method_name, object)
88
+ message = case object
89
+ when InstanceVerifyingDouble
90
+ "the %s class does not implement the instance method: %s".dup <<
91
+ if ObjectMethodReference.for(doubled_module, method_name).implemented?
92
+ ". Perhaps you meant to use `class_double` instead?"
93
+ else
94
+ ""
95
+ end
96
+ when ClassVerifyingDouble
97
+ "the %s class does not implement the class method: %s".dup <<
98
+ if InstanceMethodReference.for(doubled_module, method_name).implemented?
99
+ ". Perhaps you meant to use `instance_double` instead?"
100
+ else
101
+ ""
102
+ end
103
+ else
104
+ "%s does not implement: %s"
105
+ end
106
+
107
+ __raise message % [doubled_module.description, method_name]
73
108
  end
74
109
 
75
110
  # @private
@@ -87,161 +122,269 @@ module RSpec
87
122
  # @private
88
123
  def raise_expired_test_double_error
89
124
  raise ExpiredTestDoubleError,
90
- "#{intro} was originally created in one example but has leaked into " +
91
- "another example and can no longer be used. rspec-mocks' doubles are " +
92
- "designed to only last for one example, and you need to create a new " +
93
- "one in each example you wish to use it for."
125
+ "#{intro} was originally created in one example but has leaked into " \
126
+ "another example and can no longer be used. rspec-mocks' doubles are " \
127
+ "designed to only last for one example, and you need to create a new " \
128
+ "one in each example you wish to use it for."
94
129
  end
95
130
 
96
131
  # @private
97
- def received_part_of_expectation_error(actual_received_count, *args)
98
- "received: #{count_message(actual_received_count)}" +
99
- actual_method_call_args_description(actual_received_count, args)
132
+ def describe_expectation(verb, message, expected_received_count, _actual_received_count, args)
133
+ "#{verb} #{message}#{format_args(args)} #{count_message(expected_received_count)}"
100
134
  end
101
135
 
102
136
  # @private
103
- def expected_part_of_expectation_error(expected_received_count, expectation_count_type, argument_list_matcher)
104
- "expected: #{count_message(expected_received_count, expectation_count_type)}" +
105
- expected_method_call_args_description(argument_list_matcher.expected_args)
137
+ def raise_out_of_order_error(message)
138
+ __raise "#{intro} received :#{message} out of order"
106
139
  end
107
140
 
108
141
  # @private
109
- def actual_method_call_args_description(count, args)
110
- method_call_args_description(args) ||
111
- if count > 0 && args.length > 0
112
- " with arguments: #{args.inspect.gsub(/\A\[(.+)\]\z/, '(\1)')}"
113
- else
114
- ""
115
- end
142
+ def raise_missing_block_error(args_to_yield)
143
+ __raise "#{intro} asked to yield |#{arg_list(args_to_yield)}| but no block was passed"
116
144
  end
117
145
 
118
146
  # @private
119
- def expected_method_call_args_description(args)
120
- method_call_args_description(args) ||
121
- if args.length > 0
122
- " with arguments: #{format_args(*args)}"
123
- else
124
- ""
125
- end
147
+ def raise_wrong_arity_error(args_to_yield, signature)
148
+ __raise "#{intro} yielded |#{arg_list(args_to_yield)}| to block with #{signature.description}"
126
149
  end
127
150
 
128
151
  # @private
129
- def method_call_args_description(args)
130
- case args.first
131
- when ArgumentMatchers::AnyArgsMatcher
132
- return " with any arguments"
133
- when ArgumentMatchers::NoArgsMatcher
134
- return " with no arguments"
135
- end
152
+ def raise_only_valid_on_a_partial_double(method)
153
+ __raise "#{intro} is a pure test double. `#{method}` is only " \
154
+ "available on a partial double."
136
155
  end
137
156
 
138
157
  # @private
139
- def describe_expectation(message, expected_received_count, actual_received_count, *args)
140
- "have received #{message}#{format_args(*args)} #{count_message(expected_received_count)}"
158
+ def raise_expectation_on_unstubbed_method(method)
159
+ __raise "#{intro} expected to have received #{method}, but that " \
160
+ "object is not a spy or method has not been stubbed."
141
161
  end
142
162
 
143
163
  # @private
144
- def raise_out_of_order_error(message)
145
- __raise "#{intro} received :#{message} out of order"
164
+ def raise_expectation_on_mocked_method(method)
165
+ __raise "#{intro} expected to have received #{method}, but that " \
166
+ "method has been mocked instead of stubbed or spied."
146
167
  end
147
168
 
148
169
  # @private
149
- def raise_block_failed_error(message, detail)
150
- __raise "#{intro} received :#{message} but passed block failed with: #{detail}"
170
+ def raise_double_negation_error(wrapped_expression)
171
+ __raise "Isn't life confusing enough? You've already set a " \
172
+ "negative message expectation and now you are trying to " \
173
+ "negate it again with `never`. What does an expression like " \
174
+ "`#{wrapped_expression}.not_to receive(:msg).never` even mean?"
151
175
  end
152
176
 
153
177
  # @private
154
- def raise_missing_block_error(args_to_yield)
155
- __raise "#{intro} asked to yield |#{arg_list(*args_to_yield)}| but no block was passed"
178
+ def raise_verifying_double_not_defined_error(ref)
179
+ notify(VerifyingDoubleNotDefinedError.new(
180
+ "#{ref.description.inspect} is not a defined constant. " \
181
+ "Perhaps you misspelt it? " \
182
+ "Disable check with `verify_doubled_constant_names` configuration option."
183
+ ))
156
184
  end
157
185
 
158
186
  # @private
159
- def raise_wrong_arity_error(args_to_yield, signature)
160
- __raise "#{intro} yielded |#{arg_list(*args_to_yield)}| to block with #{signature.description}"
187
+ def raise_have_received_disallowed(type, reason)
188
+ __raise "Using #{type}(...) with the `have_received` " \
189
+ "matcher is not supported#{reason}."
161
190
  end
162
191
 
163
192
  # @private
164
- def raise_only_valid_on_a_partial_double(method)
165
- __raise "#{intro} is a pure test double. `#{method}` is only " +
166
- "available on a partial double."
193
+ def raise_cant_constrain_count_for_negated_have_received_error(count_constraint)
194
+ __raise "can't use #{count_constraint} when negative"
167
195
  end
168
196
 
169
197
  # @private
170
- def raise_expectation_on_unstubbed_method(method)
171
- __raise "#{intro} expected to have received #{method}, but that " +
172
- "method has not been stubbed."
198
+ def raise_method_not_stubbed_error(method_name)
199
+ __raise "The method `#{method_name}` was not stubbed or was already unstubbed"
173
200
  end
174
201
 
175
202
  # @private
176
- def raise_expectation_on_mocked_method(method)
177
- __raise "#{intro} expected to have received #{method}, but that " +
178
- "method has been mocked instead of stubbed."
203
+ def raise_already_invoked_error(message, calling_customization)
204
+ error_message = "The message expectation for #{intro}.#{message} has already been invoked " \
205
+ "and cannot be modified further (e.g. using `#{calling_customization}`). All message expectation " \
206
+ "customizations must be applied before it is used for the first time."
207
+
208
+ notify MockExpectationAlreadyInvokedError.new(error_message)
179
209
  end
180
210
 
181
- def self.raise_double_negation_error(wrapped_expression)
182
- raise "Isn't life confusing enough? You've already set a " +
183
- "negative message expectation and now you are trying to " +
184
- "negate it again with `never`. What does an expression like " +
185
- "`#{wrapped_expression}.not_to receive(:msg).never` even mean?"
211
+ def raise_expectation_on_nil_error(method_name)
212
+ __raise expectation_on_nil_message(method_name)
186
213
  end
187
214
 
188
- private
215
+ def expectation_on_nil_message(method_name)
216
+ "An expectation of `:#{method_name}` was set on `nil`. " \
217
+ "To allow expectations on `nil` and suppress this message, set `RSpec::Mocks.configuration.allow_message_expectations_on_nil` to `true`. " \
218
+ "To disallow expectations on `nil`, set `RSpec::Mocks.configuration.allow_message_expectations_on_nil` to `false`"
219
+ end
220
+
221
+ # @private
222
+ def intro(unwrapped=false)
223
+ case @target
224
+ when TestDouble then TestDoubleFormatter.format(@target, unwrapped)
225
+ when Class then
226
+ formatted = "#{@target.inspect} (class)"
227
+ return formatted if unwrapped
228
+ "#<#{formatted}>"
229
+ when NilClass then "nil"
230
+ else @target.inspect
231
+ end
232
+ end
189
233
 
190
- def intro
191
- if @name
192
- "Double #{@name.inspect}"
193
- elsif TestDouble === @target
194
- "Double"
195
- elsif Class === @target
196
- "<#{@target.inspect} (class)>"
197
- elsif @target
198
- @target
234
+ # @private
235
+ def method_call_args_description(args, generic_prefix=" with arguments: ", matcher_prefix=" with ")
236
+ case args.first
237
+ when ArgumentMatchers::AnyArgsMatcher then "#{matcher_prefix}any arguments"
238
+ when ArgumentMatchers::NoArgsMatcher then "#{matcher_prefix}no arguments"
199
239
  else
200
- "nil"
240
+ if yield
241
+ "#{generic_prefix}#{format_args(args)}"
242
+ else
243
+ ""
244
+ end
201
245
  end
202
246
  end
203
247
 
204
- def __raise(message)
205
- message = opts[:message] unless opts[:message].nil?
206
- Kernel::raise(RSpec::Mocks::MockExpectationError, message)
248
+ private
249
+
250
+ def received_part_of_expectation_error(actual_received_count, args)
251
+ "received: #{count_message(actual_received_count)}" +
252
+ method_call_args_description(args) do
253
+ actual_received_count > 0 && args.length > 0
254
+ end
255
+ end
256
+
257
+ def expected_part_of_expectation_error(expected_received_count, expectation_count_type, argument_list_matcher)
258
+ "expected: #{count_message(expected_received_count, expectation_count_type)}" +
259
+ method_call_args_description(argument_list_matcher.expected_args) do
260
+ argument_list_matcher.expected_args.length > 0
261
+ end
262
+ end
263
+
264
+ def unexpected_arguments_message(expected_args_string, actual_args_string)
265
+ "with unexpected arguments\n expected: #{expected_args_string}\n got: #{actual_args_string}"
266
+ end
267
+
268
+ def error_message(expectation, args_for_multiple_calls)
269
+ expected_args = format_args(expectation.expected_args)
270
+ actual_args = format_received_args(args_for_multiple_calls)
271
+
272
+ if RSpec::Support::RubyFeatures.distincts_kw_args_from_positional_hash?
273
+ expected_hash = expectation.expected_args.last
274
+ actual_hash = args_for_multiple_calls.last.last
275
+ if Hash === expected_hash && Hash === actual_hash &&
276
+ (Hash.ruby2_keywords_hash?(expected_hash) != Hash.ruby2_keywords_hash?(actual_hash))
277
+
278
+ actual_description = Hash.ruby2_keywords_hash?(actual_hash) ? " (keyword arguments)" : " (options hash)"
279
+ expected_description = Hash.ruby2_keywords_hash?(expected_hash) ? " (keyword arguments)" : " (options hash)"
280
+
281
+ if actual_description != expected_description
282
+ actual_args += actual_description
283
+ expected_args += expected_description
284
+ end
285
+ end
286
+ end
287
+
288
+ message = default_error_message(expectation, expected_args, actual_args)
289
+
290
+ if args_for_multiple_calls.one?
291
+ diff = diff_message(expectation.expected_args, args_for_multiple_calls.first)
292
+ if RSpec::Mocks.configuration.color?
293
+ message << "\nDiff:#{diff}" unless diff.gsub(/\e\[\d+m/, '').strip.empty?
294
+ else
295
+ message << "\nDiff:#{diff}" unless diff.strip.empty?
296
+ end
297
+ end
298
+
299
+ message
300
+ end
301
+
302
+ def diff_message(expected_args, actual_args)
303
+ formatted_expected_args = expected_args.map do |x|
304
+ RSpec::Support.rspec_description_for_object(x)
305
+ end
306
+
307
+ formatted_expected_args, actual_args = unpack_string_args(formatted_expected_args, actual_args)
308
+
309
+ differ.diff(actual_args, formatted_expected_args)
310
+ end
311
+
312
+ def unpack_string_args(formatted_expected_args, actual_args)
313
+ if [formatted_expected_args, actual_args].all? { |x| list_of_exactly_one_string?(x) }
314
+ [formatted_expected_args.first, actual_args.first]
315
+ else
316
+ [formatted_expected_args, actual_args]
317
+ end
318
+ end
319
+
320
+ def list_of_exactly_one_string?(args)
321
+ Array === args && args.count == 1 && String === args.first
207
322
  end
208
323
 
209
- def arg_message(*args)
210
- " with " + format_args(*args)
324
+ def differ
325
+ RSpec::Support::Differ.new(:color => RSpec::Mocks.configuration.color?)
211
326
  end
212
327
 
213
- def format_args(*args)
214
- args.empty? ? "(no args)" : "(" + arg_list(*args) + ")"
328
+ def __raise(message, backtrace_line=nil, source_id=nil)
329
+ message = opts[:message] unless opts[:message].nil?
330
+ exception = RSpec::Mocks::MockExpectationError.new(message)
331
+ prepend_to_backtrace(exception, backtrace_line) if backtrace_line
332
+ notify exception, :source_id => source_id
215
333
  end
216
334
 
217
- def arg_list(*args)
218
- args.collect {|arg| arg_has_valid_description(arg) ? arg.description : arg.inspect }.join(", ")
335
+ if RSpec::Support::Ruby.jruby?
336
+ def prepend_to_backtrace(exception, line)
337
+ raise exception
338
+ rescue RSpec::Mocks::MockExpectationError => with_backtrace
339
+ with_backtrace.backtrace.unshift(line)
340
+ end
341
+ else
342
+ def prepend_to_backtrace(exception, line)
343
+ exception.set_backtrace(caller.unshift line)
344
+ end
219
345
  end
220
346
 
221
- def arg_has_valid_description(arg)
222
- return false unless arg.respond_to?(:description)
347
+ def notify(*args)
348
+ RSpec::Support.notify_failure(*args)
349
+ end
223
350
 
224
- !arg.description.nil? && !arg.description.empty?
351
+ def format_args(args)
352
+ return "(no args)" if args.empty?
353
+ "(#{arg_list(args)})"
225
354
  end
226
355
 
227
- def format_received_args(*args)
228
- args.empty? ? "(no args)" : "(" + received_arg_list(*args) + ")"
356
+ def arg_list(args)
357
+ args.map { |arg| RSpec::Support::ObjectFormatter.format(arg) }.join(", ")
229
358
  end
230
359
 
231
- def received_arg_list(*args)
232
- args.collect(&:inspect).join(", ")
360
+ def format_received_args(args_for_multiple_calls)
361
+ grouped_args(args_for_multiple_calls).map do |args_for_one_call, index|
362
+ "#{format_args(args_for_one_call)}#{group_count(index, args_for_multiple_calls)}"
363
+ end.join("\n ")
233
364
  end
234
365
 
235
366
  def count_message(count, expectation_count_type=nil)
236
367
  return "at least #{times(count.abs)}" if count < 0 || expectation_count_type == :at_least
237
368
  return "at most #{times(count)}" if expectation_count_type == :at_most
238
- return times(count)
369
+ times(count)
239
370
  end
240
371
 
241
372
  def times(count)
242
373
  "#{count} time#{count == 1 ? '' : 's'}"
243
374
  end
244
375
 
376
+ def grouped_args(args)
377
+ Hash[args.group_by { |x| x }.map { |k, v| [k, v.count] }]
378
+ end
379
+
380
+ def group_count(index, args)
381
+ " (#{times(index)})" if args.size > 1 || index > 1
382
+ end
383
+ end
384
+
385
+ # @private
386
+ def self.error_generator
387
+ @error_generator ||= ErrorGenerator.new
245
388
  end
246
389
  end
247
390
  end