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
@@ -11,11 +11,11 @@ module RSpec
11
11
 
12
12
  # @overload double()
13
13
  # @overload double(name)
14
- # @param name [String/Symbol] used to clarify intent
14
+ # @param name [String/Symbol] name or description to be used in failure messages
15
15
  # @overload double(stubs)
16
16
  # @param stubs (Hash) hash of message/return-value pairs
17
17
  # @overload double(name, stubs)
18
- # @param name [String/Symbol] used to clarify intent
18
+ # @param name [String/Symbol] name or description to be used in failure messages
19
19
  # @param stubs (Hash) hash of message/return-value pairs
20
20
  # @return (Double)
21
21
  #
@@ -24,7 +24,6 @@ module RSpec
24
24
  # hash of message/return-value pairs.
25
25
  #
26
26
  # @example
27
- #
28
27
  # book = double("book", :title => "The RSpec Book")
29
28
  # book.title #=> "The RSpec Book"
30
29
  #
@@ -38,9 +37,16 @@ module RSpec
38
37
 
39
38
  # @overload instance_double(doubled_class)
40
39
  # @param doubled_class [String, Class]
40
+ # @overload instance_double(doubled_class, name)
41
+ # @param doubled_class [String, Class]
42
+ # @param name [String/Symbol] name or description to be used in failure messages
41
43
  # @overload instance_double(doubled_class, stubs)
42
44
  # @param doubled_class [String, Class]
43
45
  # @param stubs [Hash] hash of message/return-value pairs
46
+ # @overload instance_double(doubled_class, name, stubs)
47
+ # @param doubled_class [String, Class]
48
+ # @param name [String/Symbol] name or description to be used in failure messages
49
+ # @param stubs [Hash] hash of message/return-value pairs
44
50
  # @return InstanceVerifyingDouble
45
51
  #
46
52
  # Constructs a test double against a specific class. If the given class
@@ -54,9 +60,16 @@ module RSpec
54
60
 
55
61
  # @overload class_double(doubled_class)
56
62
  # @param doubled_class [String, Module]
63
+ # @overload class_double(doubled_class, name)
64
+ # @param doubled_class [String, Module]
65
+ # @param name [String/Symbol] name or description to be used in failure messages
57
66
  # @overload class_double(doubled_class, stubs)
58
67
  # @param doubled_class [String, Module]
59
68
  # @param stubs [Hash] hash of message/return-value pairs
69
+ # @overload class_double(doubled_class, name, stubs)
70
+ # @param doubled_class [String, Module]
71
+ # @param name [String/Symbol] name or description to be used in failure messages
72
+ # @param stubs [Hash] hash of message/return-value pairs
60
73
  # @return ClassVerifyingDouble
61
74
  #
62
75
  # Constructs a test double against a specific class. If the given class
@@ -70,9 +83,16 @@ module RSpec
70
83
 
71
84
  # @overload object_double(object_or_name)
72
85
  # @param object_or_name [String, Object]
86
+ # @overload object_double(object_or_name, name)
87
+ # @param object_or_name [String, Object]
88
+ # @param name [String/Symbol] name or description to be used in failure messages
73
89
  # @overload object_double(object_or_name, stubs)
74
90
  # @param object_or_name [String, Object]
75
91
  # @param stubs [Hash] hash of message/return-value pairs
92
+ # @overload object_double(object_or_name, name, stubs)
93
+ # @param object_or_name [String, Object]
94
+ # @param name [String/Symbol] name or description to be used in failure messages
95
+ # @param stubs [Hash] hash of message/return-value pairs
76
96
  # @return ObjectVerifyingDouble
77
97
  #
78
98
  # Constructs a test double against a specific object. Only the methods
@@ -84,11 +104,100 @@ module RSpec
84
104
  ExampleMethods.declare_verifying_double(ObjectVerifyingDouble, ref, *args)
85
105
  end
86
106
 
107
+ # @overload spy()
108
+ # @overload spy(name)
109
+ # @param name [String/Symbol] name or description to be used in failure messages
110
+ # @overload spy(stubs)
111
+ # @param stubs (Hash) hash of message/return-value pairs
112
+ # @overload spy(name, stubs)
113
+ # @param name [String/Symbol] name or description to be used in failure messages
114
+ # @param stubs (Hash) hash of message/return-value pairs
115
+ # @return (Double)
116
+ #
117
+ # Constructs a test double that is optimized for use with
118
+ # `have_received`. With a normal double one has to stub methods in order
119
+ # to be able to spy them. A spy automatically spies on all methods.
120
+ def spy(*args)
121
+ double(*args).as_null_object
122
+ end
123
+
124
+ # @overload instance_spy(doubled_class)
125
+ # @param doubled_class [String, Class]
126
+ # @overload instance_spy(doubled_class, name)
127
+ # @param doubled_class [String, Class]
128
+ # @param name [String/Symbol] name or description to be used in failure messages
129
+ # @overload instance_spy(doubled_class, stubs)
130
+ # @param doubled_class [String, Class]
131
+ # @param stubs [Hash] hash of message/return-value pairs
132
+ # @overload instance_spy(doubled_class, name, stubs)
133
+ # @param doubled_class [String, Class]
134
+ # @param name [String/Symbol] name or description to be used in failure messages
135
+ # @param stubs [Hash] hash of message/return-value pairs
136
+ # @return InstanceVerifyingDouble
137
+ #
138
+ # Constructs a test double that is optimized for use with `have_received`
139
+ # against a specific class. If the given class name has been loaded, only
140
+ # instance methods defined on the class are allowed to be stubbed. With
141
+ # a normal double one has to stub methods in order to be able to spy
142
+ # them. An instance_spy automatically spies on all instance methods to
143
+ # which the class responds.
144
+ def instance_spy(*args)
145
+ instance_double(*args).as_null_object
146
+ end
147
+
148
+ # @overload object_spy(object_or_name)
149
+ # @param object_or_name [String, Object]
150
+ # @overload object_spy(object_or_name, name)
151
+ # @param object_or_name [String, Class]
152
+ # @param name [String/Symbol] name or description to be used in failure messages
153
+ # @overload object_spy(object_or_name, stubs)
154
+ # @param object_or_name [String, Object]
155
+ # @param stubs [Hash] hash of message/return-value pairs
156
+ # @overload object_spy(object_or_name, name, stubs)
157
+ # @param object_or_name [String, Class]
158
+ # @param name [String/Symbol] name or description to be used in failure messages
159
+ # @param stubs [Hash] hash of message/return-value pairs
160
+ # @return ObjectVerifyingDouble
161
+ #
162
+ # Constructs a test double that is optimized for use with `have_received`
163
+ # against a specific object. Only instance methods defined on the object
164
+ # are allowed to be stubbed. With a normal double one has to stub
165
+ # methods in order to be able to spy them. An object_spy automatically
166
+ # spies on all methods to which the object responds.
167
+ def object_spy(*args)
168
+ object_double(*args).as_null_object
169
+ end
170
+
171
+ # @overload class_spy(doubled_class)
172
+ # @param doubled_class [String, Module]
173
+ # @overload class_spy(doubled_class, name)
174
+ # @param doubled_class [String, Class]
175
+ # @param name [String/Symbol] name or description to be used in failure messages
176
+ # @overload class_spy(doubled_class, stubs)
177
+ # @param doubled_class [String, Module]
178
+ # @param stubs [Hash] hash of message/return-value pairs
179
+ # @overload class_spy(doubled_class, name, stubs)
180
+ # @param doubled_class [String, Class]
181
+ # @param name [String/Symbol] name or description to be used in failure messages
182
+ # @param stubs [Hash] hash of message/return-value pairs
183
+ # @return ClassVerifyingDouble
184
+ #
185
+ # Constructs a test double that is optimized for use with `have_received`
186
+ # against a specific class. If the given class name has been loaded,
187
+ # only class methods defined on the class are allowed to be stubbed.
188
+ # With a normal double one has to stub methods in order to be able to spy
189
+ # them. An class_spy automatically spies on all class methods to which the
190
+ # class responds.
191
+ def class_spy(*args)
192
+ class_double(*args).as_null_object
193
+ end
194
+
87
195
  # Disables warning messages about expectations being set on nil.
88
196
  #
89
197
  # By default warning messages are issued when expectations are set on
90
198
  # nil. This is to prevent false-positives and to catch potential bugs
91
199
  # early on.
200
+ # @deprecated Use {RSpec::Mocks::Configuration#allow_message_expectations_on_nil} instead.
92
201
  def allow_message_expectations_on_nil
93
202
  RSpec::Mocks.space.proxy_for(nil).warn_about_expectations = false
94
203
  end
@@ -110,7 +219,6 @@ module RSpec
110
219
  # @return [Object] the stubbed value of the constant
111
220
  #
112
221
  # @example
113
- #
114
222
  # stub_const("MyClass", Class.new) # => Replaces (or defines) MyClass with a new class object.
115
223
  # stub_const("SomeModel::PER_PAGE", 5) # => Sets SomeModel::PER_PAGE to 5.
116
224
  #
@@ -130,7 +238,7 @@ module RSpec
130
238
  # stub_const("CardDeck", Class.new, :transfer_nested_constants => [:SUITS])
131
239
  # CardDeck::SUITS # => our suits array
132
240
  # CardDeck::NUM_CARDS # => uninitialized constant error
133
- def stub_const(constant_name, value, options = {})
241
+ def stub_const(constant_name, value, options={})
134
242
  ConstantMutator.stub(constant_name, value, options)
135
243
  end
136
244
 
@@ -144,15 +252,15 @@ module RSpec
144
252
  # The current constant scoping at the point of call is not considered.
145
253
  #
146
254
  # @example
147
- #
148
255
  # hide_const("MyClass") # => MyClass is now an undefined constant
149
256
  def hide_const(constant_name)
150
257
  ConstantMutator.hide(constant_name)
151
258
  end
152
259
 
153
260
  # Verifies that the given object received the expected message during the
154
- # course of the test. The method must have previously been stubbed in
155
- # order for messages to be verified.
261
+ # course of the test. On a spy objects or as null object doubles this
262
+ # works for any method, on other objects the method must have
263
+ # been stubbed beforehand in order for messages to be verified.
156
264
  #
157
265
  # Stubbing and verifying messages received in this way implements the
158
266
  # Test Spy pattern.
@@ -161,23 +269,36 @@ module RSpec
161
269
  # called.
162
270
  #
163
271
  # @example
164
- #
165
272
  # invitation = double('invitation', accept: true)
166
273
  # user.accept_invitation(invitation)
167
274
  # expect(invitation).to have_received(:accept)
168
275
  #
169
276
  # # You can also use most message expectations:
170
277
  # expect(invitation).to have_received(:accept).with(mailer).once
278
+ #
279
+ # @note `have_received(...).with(...)` is unable to work properly when
280
+ # passed arguments are mutated after the spy records the received message.
171
281
  def have_received(method_name, &block)
172
282
  Matchers::HaveReceived.new(method_name, &block)
173
283
  end
174
284
 
285
+ # Turns off the verifying of partial doubles for the duration of the
286
+ # block, this is useful in situations where methods are defined at run
287
+ # time and you wish to define stubs for them but not turn off partial
288
+ # doubles for the entire run suite. (e.g. view specs in rspec-rails).
289
+ def without_partial_double_verification
290
+ original_state = Mocks.configuration.temporarily_suppress_partial_double_verification
291
+ Mocks.configuration.temporarily_suppress_partial_double_verification = true
292
+ yield
293
+ ensure
294
+ Mocks.configuration.temporarily_suppress_partial_double_verification = original_state
295
+ end
296
+
175
297
  # @method expect
176
298
  # Used to wrap an object in preparation for setting a mock expectation
177
299
  # on it.
178
300
  #
179
301
  # @example
180
- #
181
302
  # expect(obj).to receive(:foo).with(5).and_return(:return_value)
182
303
  #
183
304
  # @note This method is usually provided by rspec-expectations. However,
@@ -190,7 +311,6 @@ module RSpec
190
311
  # on it.
191
312
  #
192
313
  # @example
193
- #
194
314
  # allow(dbl).to receive(:foo).with(5).and_return(:return_value)
195
315
  #
196
316
  # @note If you disable the `:expect` syntax this method will be undefined.
@@ -200,7 +320,6 @@ module RSpec
200
320
  # on instances of it.
201
321
  #
202
322
  # @example
203
- #
204
323
  # expect_any_instance_of(MyClass).to receive(:foo)
205
324
  #
206
325
  # @note If you disable the `:expect` syntax this method will be undefined.
@@ -210,7 +329,6 @@ module RSpec
210
329
  # on instances of it.
211
330
  #
212
331
  # @example
213
- #
214
332
  # allow_any_instance_of(MyClass).to receive(:foo)
215
333
  #
216
334
  # @note This is only available when you have enabled the `expect` syntax.
@@ -223,7 +341,6 @@ module RSpec
223
341
  # times, and configure how the object should respond to the message.
224
342
  #
225
343
  # @example
226
- #
227
344
  # expect(obj).to receive(:hello).with("world").exactly(3).times
228
345
  #
229
346
  # @note If you disable the `:expect` syntax this method will be undefined.
@@ -236,7 +353,6 @@ module RSpec
236
353
  # interface.
237
354
  #
238
355
  # @example
239
- #
240
356
  # allow(obj).to receive_messages(:speak => "Hello World")
241
357
  # allow(obj).to receive_messages(:speak => "Hello", :meow => "Meow")
242
358
  #
@@ -260,16 +376,15 @@ module RSpec
260
376
  # implementation calls `foo.baz.bar`, the stub will not work.
261
377
  #
262
378
  # @example
379
+ # allow(double).to receive_message_chain("foo.bar") { :baz }
380
+ # allow(double).to receive_message_chain(:foo, :bar => :baz)
381
+ # allow(double).to receive_message_chain(:foo, :bar) { :baz }
263
382
  #
264
- # allow(double).to receive_message_chain("foo.bar") { :baz }
265
- # allow(double).to receive_message_chain(:foo, :bar => :baz)
266
- # allow(double).to receive_message_chain(:foo, :bar) { :baz }
267
- #
268
- # # Given any of ^^ these three forms ^^:
269
- # double.foo.bar # => :baz
383
+ # # Given any of ^^ these three forms ^^:
384
+ # double.foo.bar # => :baz
270
385
  #
271
- # # Common use in Rails/ActiveRecord:
272
- # allow(Article).to receive_message_chain("recent.published") { [Article.new] }
386
+ # # Common use in Rails/ActiveRecord:
387
+ # allow(Article).to receive_message_chain("recent.published") { [Article.new] }
273
388
  #
274
389
  # @note If you disable the `:expect` syntax this method will be undefined.
275
390
 
@@ -277,20 +392,28 @@ module RSpec
277
392
  def self.included(klass)
278
393
  klass.class_exec do
279
394
  # This gets mixed in so that if `RSpec::Matchers` is included in
280
- # `klass` later, it's definition of `expect` will take precedence.
395
+ # `klass` later, its definition of `expect` will take precedence.
281
396
  include ExpectHost unless method_defined?(:expect)
282
397
  end
283
398
  end
284
399
 
400
+ # @private
401
+ def self.extended(object)
402
+ # This gets extended in so that if `RSpec::Matchers` is included in
403
+ # `klass` later, its definition of `expect` will take precedence.
404
+ object.extend ExpectHost unless object.respond_to?(:expect)
405
+ end
406
+
285
407
  # @private
286
408
  def self.declare_verifying_double(type, ref, *args)
287
409
  if RSpec::Mocks.configuration.verify_doubled_constant_names? &&
288
410
  !ref.defined?
289
411
 
290
- raise VerifyingDoubleNotDefinedError,
291
- "#{ref.description} is not a defined constant. " +
292
- "Perhaps you misspelt it? " +
293
- "Disable check with verify_doubled_constant_names configuration option."
412
+ RSpec::Mocks.error_generator.raise_verifying_double_not_defined_error(ref)
413
+ end
414
+
415
+ RSpec::Mocks.configuration.verifying_double_callbacks.each do |block|
416
+ block.call(ref)
294
417
  end
295
418
 
296
419
  declare_double(type, ref, *args)
@@ -31,7 +31,6 @@ module RSpec
31
31
  def stashed_method_name
32
32
  "obfuscated_by_rspec_mocks__#{@method}"
33
33
  end
34
- private :stashed_method_name
35
34
 
36
35
  # @private
37
36
  def restore
@@ -53,8 +52,9 @@ module RSpec
53
52
 
54
53
  # @private
55
54
  def stash
56
- return if !method_defined_directly_on_klass?
55
+ return unless method_defined_directly_on_klass?
57
56
  @original_method ||= ::RSpec::Support.method_handle_for(@object, @method)
57
+ @klass.__send__(:undef_method, @method)
58
58
  end
59
59
 
60
60
  # @private
@@ -80,9 +80,9 @@ module RSpec
80
80
  yield
81
81
  rescue TypeError
82
82
  RSpec.warn_with(
83
- "RSpec failed to properly restore a partial double (#{@object.inspect}) " +
84
- "to its original state due to a known bug in MRI 2.0.0-p195 & p247 " +
85
- "(https://bugs.ruby-lang.org/issues/8686). This object may remain " +
83
+ "RSpec failed to properly restore a partial double (#{@object.inspect}) " \
84
+ "to its original state due to a known bug in MRI 2.0.0-p195 & p247 " \
85
+ "(https://bugs.ruby-lang.org/issues/8686). This object may remain " \
86
86
  "screwed up for the rest of this process. Please upgrade to 2.0.0-p353 or above.",
87
87
  :call_site => nil, :use_spec_location_as_call_site => true
88
88
  )
@@ -102,7 +102,7 @@ module RSpec
102
102
  end
103
103
 
104
104
  # @private
105
- def method_defined_on_klass?(klass = @klass)
105
+ def method_defined_on_klass?(klass=@klass)
106
106
  MethodReference.method_defined_at_any_visibility?(klass, @method)
107
107
  end
108
108
 
@@ -128,6 +128,17 @@ module RSpec
128
128
  # Hence, we verify that the owner actually has the method defined.
129
129
  # If the given owner does not have the method defined, we assume
130
130
  # that the method is actually owned by @klass.
131
+ #
132
+ # On 1.8, aliased methods can also report the wrong owner. Example:
133
+ # module M
134
+ # def a; end
135
+ # module_function :a
136
+ # alias b a
137
+ # module_function :b
138
+ # end
139
+ # The owner of M.b is the raw Module object, instead of the expected
140
+ # singleton class of the module
141
+ return true if RUBY_VERSION < '1.9' && owner == @object
131
142
  owner == @klass || !(method_defined_on_klass?(owner))
132
143
  end
133
144
  end
@@ -3,9 +3,11 @@ module RSpec
3
3
  module Matchers
4
4
  # @private
5
5
  class HaveReceived
6
- COUNT_CONSTRAINTS = %w(exactly at_least at_most times once twice)
7
- ARGS_CONSTRAINTS = %w(with)
8
- CONSTRAINTS = COUNT_CONSTRAINTS + ARGS_CONSTRAINTS + %w(ordered)
6
+ include Matcher
7
+
8
+ COUNT_CONSTRAINTS = %w[exactly at_least at_most times time once twice thrice]
9
+ ARGS_CONSTRAINTS = %w[with]
10
+ CONSTRAINTS = COUNT_CONSTRAINTS + ARGS_CONSTRAINTS + %w[ordered]
9
11
 
10
12
  def initialize(method_name, &block)
11
13
  @method_name = method_name
@@ -14,7 +16,7 @@ module RSpec
14
16
  @subject = nil
15
17
  end
16
18
 
17
- def name
19
+ def matcher_name
18
20
  "have_received"
19
21
  end
20
22
 
@@ -36,15 +38,15 @@ module RSpec
36
38
  end
37
39
 
38
40
  def failure_message
39
- generate_failure_message
41
+ capture_failure_message
40
42
  end
41
43
 
42
44
  def failure_message_when_negated
43
- generate_failure_message
45
+ capture_failure_message
44
46
  end
45
47
 
46
48
  def description
47
- expect.description
49
+ (@expectation ||= expect).description_for("have received")
48
50
  end
49
51
 
50
52
  CONSTRAINTS.each do |expectation|
@@ -54,14 +56,40 @@ module RSpec
54
56
  end
55
57
  end
56
58
 
59
+ def setup_expectation(subject, &block)
60
+ notify_failure_message unless matches?(subject, &block)
61
+ end
62
+
63
+ def setup_negative_expectation(subject, &block)
64
+ notify_failure_message unless does_not_match?(subject, &block)
65
+ end
66
+
67
+ def setup_allowance(_subject, &_block)
68
+ disallow("allow", " as it would have no effect")
69
+ end
70
+
71
+ def setup_any_instance_allowance(_subject, &_block)
72
+ disallow("allow_any_instance_of")
73
+ end
74
+
75
+ def setup_any_instance_expectation(_subject, &_block)
76
+ disallow("expect_any_instance_of")
77
+ end
78
+
79
+ def setup_any_instance_negative_expectation(_subject, &_block)
80
+ disallow("expect_any_instance_of")
81
+ end
82
+
57
83
  private
58
84
 
85
+ def disallow(type, reason="")
86
+ RSpec::Mocks.error_generator.raise_have_received_disallowed(type, reason)
87
+ end
88
+
59
89
  def expect
60
- @expectation ||= begin
61
- expectation = mock_proxy.build_expectation(@method_name)
62
- apply_constraints_to expectation
63
- expectation
64
- end
90
+ expectation = mock_proxy.build_expectation(@method_name)
91
+ apply_constraints_to expectation
92
+ expectation
65
93
  end
66
94
 
67
95
  def apply_constraints_to(expectation)
@@ -71,23 +99,25 @@ module RSpec
71
99
  end
72
100
 
73
101
  def ensure_count_unconstrained
74
- if count_constraint
75
- raise RSpec::Mocks::MockExpectationError,
76
- "can't use #{count_constraint} when negative"
77
- end
102
+ return unless count_constraint
103
+ RSpec::Mocks.error_generator.raise_cant_constrain_count_for_negated_have_received_error(count_constraint)
78
104
  end
79
105
 
80
106
  def count_constraint
81
- @constraints.map(&:first).detect do |constraint|
107
+ @constraints.map(&:first).find do |constraint|
82
108
  COUNT_CONSTRAINTS.include?(constraint)
83
109
  end
84
110
  end
85
111
 
86
- def generate_failure_message
112
+ def capture_failure_message
113
+ RSpec::Support.with_failure_notifier(Proc.new { |err, _opt| return err.message }) do
114
+ notify_failure_message
115
+ end
116
+ end
117
+
118
+ def notify_failure_message
87
119
  mock_proxy.check_for_unexpected_arguments(@expectation)
88
120
  @expectation.generate_error
89
- rescue RSpec::Mocks::MockExpectationError => error
90
- error.message
91
121
  end
92
122
 
93
123
  def expected_messages_received_in_order?
@@ -5,19 +5,25 @@ module RSpec
5
5
  module Matchers
6
6
  # @private
7
7
  class Receive
8
+ include Matcher
9
+
8
10
  def initialize(message, block)
9
11
  @message = message
10
12
  @block = block
11
13
  @recorded_customizations = []
12
14
  end
13
15
 
14
- def name
16
+ def matcher_name
15
17
  "receive"
16
18
  end
17
19
 
20
+ def description
21
+ describable.description_for("receive")
22
+ end
23
+
18
24
  def setup_expectation(subject, &block)
19
25
  warn_if_any_instance("expect", subject)
20
- setup_mock_proxy_method_substitute(subject, :add_message_expectation, block)
26
+ @describable = setup_mock_proxy_method_substitute(subject, :add_message_expectation, block)
21
27
  end
22
28
  alias matches? setup_expectation
23
29
 
@@ -49,26 +55,32 @@ module RSpec
49
55
  setup_any_instance_method_substitute(subject, :stub, block)
50
56
  end
51
57
 
58
+ own_methods = (instance_methods - superclass.instance_methods)
52
59
  MessageExpectation.public_instance_methods(false).each do |method|
53
- next if method_defined?(method)
60
+ next if own_methods.include?(method)
54
61
 
55
62
  define_method(method) do |*args, &block|
56
63
  @recorded_customizations << ExpectationCustomization.new(method, args, block)
57
64
  self
58
65
  end
66
+ ruby2_keywords(method) if respond_to?(:ruby2_keywords, true)
59
67
  end
60
68
 
61
69
  private
62
70
 
71
+ def describable
72
+ @describable ||= DefaultDescribable.new(@message)
73
+ end
74
+
63
75
  def warn_if_any_instance(expression, subject)
64
- if AnyInstance::Proxy === subject
65
- RSpec.warning(
66
- "`#{expression}(#{subject.klass}.any_instance).to` " <<
67
- "is probably not what you meant, it does not operate on " <<
68
- "any instance of `#{subject.klass}`. " <<
69
- "Use `#{expression}_any_instance_of(#{subject.klass}).to` instead."
70
- )
71
- end
76
+ return unless AnyInstance::Proxy === subject
77
+
78
+ RSpec.warning(
79
+ "`#{expression}(#{subject.klass}.any_instance).to` " \
80
+ "is probably not what you meant, it does not operate on " \
81
+ "any instance of `#{subject.klass}`. " \
82
+ "Use `#{expression}_any_instance_of(#{subject.klass}).to` instead."
83
+ )
72
84
  end
73
85
 
74
86
  def setup_mock_proxy_method_substitute(subject, method, block)
@@ -100,6 +112,22 @@ module RSpec
100
112
  last.block ||= block
101
113
  nil
102
114
  end
115
+
116
+ # MessageExpectation objects are able to describe themselves in detail.
117
+ # We use this as a fall back when a MessageExpectation is not available.
118
+ # @private
119
+ class DefaultDescribable
120
+ def initialize(message)
121
+ @message = message
122
+ end
123
+
124
+ # This is much simpler for the `any_instance` case than what the
125
+ # user may want, but I'm not up for putting a bunch of effort
126
+ # into full descriptions for `any_instance` expectations at this point :(.
127
+ def description_for(verb)
128
+ "#{verb} #{@message}"
129
+ end
130
+ end
103
131
  end
104
132
  end
105
133
  end
@@ -5,23 +5,29 @@ module RSpec
5
5
  module Matchers
6
6
  # @private
7
7
  class ReceiveMessageChain
8
+ include Matcher
9
+
8
10
  def initialize(chain, &block)
9
11
  @chain = chain
10
12
  @block = block
11
13
  @recorded_customizations = []
12
14
  end
13
15
 
14
- [:with, :and_return, :and_throw, :and_raise, :and_yield, :and_call_original].each do |msg|
16
+ [:with, :and_return, :and_invoke, :and_throw, :and_raise, :and_yield, :and_call_original].each do |msg|
15
17
  define_method(msg) do |*args, &block|
16
18
  @recorded_customizations << ExpectationCustomization.new(msg, args, block)
17
19
  self
18
20
  end
19
21
  end
20
22
 
21
- def name
23
+ def matcher_name
22
24
  "receive_message_chain"
23
25
  end
24
26
 
27
+ def description
28
+ "receive message chain #{formatted_chain}"
29
+ end
30
+
25
31
  def setup_allowance(subject, &block)
26
32
  chain = StubChain.stub_chain_on(subject, *@chain, &(@block || block))
27
33
  replay_customizations(chain)
@@ -44,11 +50,10 @@ module RSpec
44
50
  replay_customizations(chain)
45
51
  end
46
52
 
47
- def setup_negative_expectation(*args)
48
- raise NegationUnsupportedError.new(
49
- "`expect(...).not_to receive_message_chain` is not supported " +
50
- "since it doesn't really make sense. What would it even mean?"
51
- )
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?"
52
57
  end
53
58
 
54
59
  alias matches? setup_expectation
@@ -61,6 +66,16 @@ module RSpec
61
66
  customization.playback_onto(chain)
62
67
  end
63
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
64
79
  end
65
80
  end
66
81
  end