rspec-mocks 3.0.4 → 3.12.6

Sign up to get free protection for your applications and to get access to all the features.
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