rspec-mocks 3.1.3 → 3.2.0

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.
@@ -10,6 +10,16 @@ module RSpec
10
10
  # Raised when doubles or partial doubles are used outside of the per-test lifecycle.
11
11
  OutsideOfExampleError = Class.new(StandardError)
12
12
 
13
+ # Raised when an expectation customization method (e.g. `with`,
14
+ # `and_return`) is called on a message expectation which has already been
15
+ # invoked.
16
+ MockExpectationAlreadyInvokedError = Class.new(Exception)
17
+
18
+ # Raised for situations that RSpec cannot support due to mutations made
19
+ # externally on arguments that RSpec is holding onto to use for later
20
+ # comparisons.
21
+ CannotSupportArgMutationsError = Class.new(StandardError)
22
+
13
23
  # @private
14
24
  UnsupportedMatcherError = Class.new(StandardError)
15
25
  # @private
@@ -40,28 +50,42 @@ module RSpec
40
50
  def raise_unexpected_message_args_error(expectation, *args)
41
51
  expected_args = format_args(*expectation.expected_args)
42
52
  actual_args = format_received_args(*args)
43
- __raise "#{intro} received #{expectation.message.inspect} with " \
44
- "unexpected arguments\n expected: #{expected_args}\n" \
45
- " got: #{actual_args}"
53
+ diff = diff_message(expectation.expected_args, args)
54
+
55
+ message = default_error_message(expectation, expected_args, actual_args)
56
+ message << "\nDiff:#{diff}" unless diff.empty?
57
+
58
+ __raise message
46
59
  end
47
60
 
48
61
  # @private
49
62
  def raise_missing_default_stub_error(expectation, *args)
50
63
  expected_args = format_args(*expectation.expected_args)
51
64
  actual_args = format_received_args(*args)
52
- __raise "#{intro} received #{expectation.message.inspect} with " \
53
- "unexpected arguments\n expected: #{expected_args}\n" \
54
- " got: #{actual_args}\n Please stub a default value " \
55
- "first if message might be received with other args as well. \n"
65
+ diff = diff_message(expectation.expected_args, args)
66
+
67
+ message = default_error_message(expectation, expected_args, actual_args)
68
+ message << "\nDiff:\n #{diff}" unless diff.empty?
69
+ message << "\n Please stub a default value first if message might be received with other args as well. \n"
70
+
71
+ __raise message
56
72
  end
57
73
 
58
74
  # @private
59
75
  def raise_similar_message_args_error(expectation, *args_for_multiple_calls)
60
76
  expected_args = format_args(*expectation.expected_args)
61
77
  actual_args = args_for_multiple_calls.map { |a| format_received_args(*a) }.join(", ")
62
- __raise "#{intro} received #{expectation.message.inspect} with " \
63
- "unexpected arguments\n expected: #{expected_args}\n" \
64
- " got: #{actual_args}"
78
+
79
+ __raise(default_error_message(expectation, expected_args, actual_args))
80
+ end
81
+
82
+ def default_error_message(expectation, expected_args, actual_args)
83
+ [
84
+ intro,
85
+ "received",
86
+ expectation.message.inspect,
87
+ unexpected_arguments_message(expected_args, actual_args),
88
+ ].join(" ")
65
89
  end
66
90
 
67
91
  # rubocop:disable Style/ParameterLists
@@ -143,8 +167,8 @@ module RSpec
143
167
  end
144
168
 
145
169
  # @private
146
- def describe_expectation(message, expected_received_count, _actual_received_count, *args)
147
- "have received #{message}#{format_args(*args)} #{count_message(expected_received_count)}"
170
+ def describe_expectation(verb, message, expected_received_count, _actual_received_count, *args)
171
+ "#{verb} #{message}#{format_args(*args)} #{count_message(expected_received_count)}"
148
172
  end
149
173
 
150
174
  # @private
@@ -194,6 +218,36 @@ module RSpec
194
218
 
195
219
  private
196
220
 
221
+ def unexpected_arguments_message(expected_args_string, actual_args_string)
222
+ "with unexpected arguments\n expected: #{expected_args_string}\n got: #{actual_args_string}"
223
+ end
224
+
225
+ def diff_message(expected_args, actual_args)
226
+ formatted_expected_args = expected_args.map do |x|
227
+ RSpec::Support.rspec_description_for_object(x)
228
+ end
229
+
230
+ formatted_expected_args, actual_args = unpack_string_args(formatted_expected_args, actual_args)
231
+
232
+ differ.diff(actual_args, formatted_expected_args)
233
+ end
234
+
235
+ def unpack_string_args(formatted_expected_args, actual_args)
236
+ if [formatted_expected_args, actual_args].all? { |x| list_of_exactly_one_string?(x) }
237
+ [formatted_expected_args.first, actual_args.first]
238
+ else
239
+ [formatted_expected_args, actual_args]
240
+ end
241
+ end
242
+
243
+ def list_of_exactly_one_string?(args)
244
+ Array === args && args.count == 1 && String === args.first
245
+ end
246
+
247
+ def differ
248
+ RSpec::Support::Differ.new(:color => RSpec::Mocks.configuration.color?)
249
+ end
250
+
197
251
  def intro
198
252
  if @name
199
253
  "Double #{@name.inspect}"
@@ -222,13 +276,11 @@ module RSpec
222
276
  end
223
277
 
224
278
  def arg_list(*args)
225
- args.map { |arg| arg_has_valid_description(arg) ? arg.description : arg.inspect }.join(", ")
279
+ args.map { |arg| arg_has_valid_description?(arg) ? arg.description : arg.inspect }.join(", ")
226
280
  end
227
281
 
228
- def arg_has_valid_description(arg)
229
- return false unless arg.respond_to?(:description)
230
-
231
- !arg.description.nil? && !arg.description.empty?
282
+ def arg_has_valid_description?(arg)
283
+ RSpec::Support.is_a_matcher?(arg) && arg.respond_to?(:description)
232
284
  end
233
285
 
234
286
  def format_received_args(*args)
@@ -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
  #
@@ -38,9 +38,16 @@ module RSpec
38
38
 
39
39
  # @overload instance_double(doubled_class)
40
40
  # @param doubled_class [String, Class]
41
+ # @overload instance_double(doubled_class, name)
42
+ # @param doubled_class [String, Class]
43
+ # @param name [String/Symbol] name or description to be used in failure messages
41
44
  # @overload instance_double(doubled_class, stubs)
42
45
  # @param doubled_class [String, Class]
43
46
  # @param stubs [Hash] hash of message/return-value pairs
47
+ # @overload instance_double(doubled_class, name, stubs)
48
+ # @param doubled_class [String, Class]
49
+ # @param name [String/Symbol] name or description to be used in failure messages
50
+ # @param stubs [Hash] hash of message/return-value pairs
44
51
  # @return InstanceVerifyingDouble
45
52
  #
46
53
  # Constructs a test double against a specific class. If the given class
@@ -54,9 +61,16 @@ module RSpec
54
61
 
55
62
  # @overload class_double(doubled_class)
56
63
  # @param doubled_class [String, Module]
64
+ # @overload class_double(doubled_class, name)
65
+ # @param doubled_class [String, Module]
66
+ # @param name [String/Symbol] name or description to be used in failure messages
57
67
  # @overload class_double(doubled_class, stubs)
58
68
  # @param doubled_class [String, Module]
59
69
  # @param stubs [Hash] hash of message/return-value pairs
70
+ # @overload class_double(doubled_class, name, stubs)
71
+ # @param doubled_class [String, Module]
72
+ # @param name [String/Symbol] name or description to be used in failure messages
73
+ # @param stubs [Hash] hash of message/return-value pairs
60
74
  # @return ClassVerifyingDouble
61
75
  #
62
76
  # Constructs a test double against a specific class. If the given class
@@ -70,9 +84,16 @@ module RSpec
70
84
 
71
85
  # @overload object_double(object_or_name)
72
86
  # @param object_or_name [String, Object]
87
+ # @overload object_double(object_or_name, name)
88
+ # @param object_or_name [String, Object]
89
+ # @param name [String/Symbol] name or description to be used in failure messages
73
90
  # @overload object_double(object_or_name, stubs)
74
91
  # @param object_or_name [String, Object]
75
92
  # @param stubs [Hash] hash of message/return-value pairs
93
+ # @overload object_double(object_or_name, name, stubs)
94
+ # @param object_or_name [String, Object]
95
+ # @param name [String/Symbol] name or description to be used in failure messages
96
+ # @param stubs [Hash] hash of message/return-value pairs
76
97
  # @return ObjectVerifyingDouble
77
98
  #
78
99
  # Constructs a test double against a specific object. Only the methods
@@ -86,11 +107,11 @@ module RSpec
86
107
 
87
108
  # @overload spy()
88
109
  # @overload spy(name)
89
- # @param name [String/Symbol] used to clarify intent
110
+ # @param name [String/Symbol] name or description to be used in failure messages
90
111
  # @overload spy(stubs)
91
112
  # @param stubs (Hash) hash of message/return-value pairs
92
113
  # @overload spy(name, stubs)
93
- # @param name [String/Symbol] used to clarify intent
114
+ # @param name [String/Symbol] name or description to be used in failure messages
94
115
  # @param stubs (Hash) hash of message/return-value pairs
95
116
  # @return (Double)
96
117
  #
@@ -103,9 +124,16 @@ module RSpec
103
124
 
104
125
  # @overload instance_spy(doubled_class)
105
126
  # @param doubled_class [String, Class]
127
+ # @overload instance_spy(doubled_class, name)
128
+ # @param doubled_class [String, Class]
129
+ # @param name [String/Symbol] name or description to be used in failure messages
106
130
  # @overload instance_spy(doubled_class, stubs)
107
131
  # @param doubled_class [String, Class]
108
132
  # @param stubs [Hash] hash of message/return-value pairs
133
+ # @overload instance_spy(doubled_class, name, stubs)
134
+ # @param doubled_class [String, Class]
135
+ # @param name [String/Symbol] name or description to be used in failure messages
136
+ # @param stubs [Hash] hash of message/return-value pairs
109
137
  # @return InstanceVerifyingDouble
110
138
  #
111
139
  # Constructs a test double that is optimized for use with `have_received`
@@ -120,9 +148,16 @@ module RSpec
120
148
 
121
149
  # @overload object_spy(object_or_name)
122
150
  # @param object_or_name [String, Object]
151
+ # @overload object_spy(object_or_name, name)
152
+ # @param object_or_name [String, Class]
153
+ # @param name [String/Symbol] name or description to be used in failure messages
123
154
  # @overload object_spy(object_or_name, stubs)
124
155
  # @param object_or_name [String, Object]
125
156
  # @param stubs [Hash] hash of message/return-value pairs
157
+ # @overload object_spy(object_or_name, name, stubs)
158
+ # @param object_or_name [String, Class]
159
+ # @param name [String/Symbol] name or description to be used in failure messages
160
+ # @param stubs [Hash] hash of message/return-value pairs
126
161
  # @return ObjectVerifyingDouble
127
162
  #
128
163
  # Constructs a test double that is optimized for use with `have_received`
@@ -136,9 +171,16 @@ module RSpec
136
171
 
137
172
  # @overload class_spy(doubled_class)
138
173
  # @param doubled_class [String, Module]
174
+ # @overload class_spy(doubled_class, name)
175
+ # @param doubled_class [String, Class]
176
+ # @param name [String/Symbol] name or description to be used in failure messages
139
177
  # @overload class_spy(doubled_class, stubs)
140
178
  # @param doubled_class [String, Module]
141
179
  # @param stubs [Hash] hash of message/return-value pairs
180
+ # @overload class_spy(doubled_class, name, stubs)
181
+ # @param doubled_class [String, Class]
182
+ # @param name [String/Symbol] name or description to be used in failure messages
183
+ # @param stubs [Hash] hash of message/return-value pairs
142
184
  # @return ClassVerifyingDouble
143
185
  #
144
186
  # Constructs a test double that is optimized for use with `have_received`
@@ -350,15 +392,26 @@ module RSpec
350
392
  end
351
393
  end
352
394
 
395
+ # @private
396
+ def self.extended(object)
397
+ # This gets extended in so that if `RSpec::Matchers` is included in
398
+ # `klass` later, it's definition of `expect` will take precedence.
399
+ object.extend ExpectHost unless object.respond_to?(:expect)
400
+ end
401
+
353
402
  # @private
354
403
  def self.declare_verifying_double(type, ref, *args)
355
404
  if RSpec::Mocks.configuration.verify_doubled_constant_names? &&
356
405
  !ref.defined?
357
406
 
358
407
  raise VerifyingDoubleNotDefinedError,
359
- "#{ref.description} is not a defined constant. " \
408
+ "#{ref.description.inspect} is not a defined constant. " \
360
409
  "Perhaps you misspelt it? " \
361
- "Disable check with verify_doubled_constant_names configuration option."
410
+ "Disable check with `verify_doubled_constant_names` configuration option."
411
+ end
412
+
413
+ RSpec::Mocks.configuration.verifying_double_declaration_callbacks.each do |block|
414
+ block.call(ref)
362
415
  end
363
416
 
364
417
  declare_double(type, ref, *args)
@@ -44,7 +44,7 @@ module RSpec
44
44
  end
45
45
 
46
46
  def description
47
- expect.description
47
+ (@expectation ||= expect).description_for("have received")
48
48
  end
49
49
 
50
50
  CONSTRAINTS.each do |expectation|
@@ -75,11 +75,9 @@ module RSpec
75
75
  end
76
76
 
77
77
  def expect
78
- @expectation ||= begin
79
- expectation = mock_proxy.build_expectation(@method_name)
80
- apply_constraints_to expectation
81
- expectation
82
- end
78
+ expectation = mock_proxy.build_expectation(@method_name)
79
+ apply_constraints_to expectation
80
+ expectation
83
81
  end
84
82
 
85
83
  def apply_constraints_to(expectation)
@@ -15,9 +15,13 @@ module RSpec
15
15
  "receive"
16
16
  end
17
17
 
18
+ def description
19
+ describable.description_for("receive")
20
+ end
21
+
18
22
  def setup_expectation(subject, &block)
19
23
  warn_if_any_instance("expect", subject)
20
- setup_mock_proxy_method_substitute(subject, :add_message_expectation, block)
24
+ @describable = setup_mock_proxy_method_substitute(subject, :add_message_expectation, block)
21
25
  end
22
26
  alias matches? setup_expectation
23
27
 
@@ -60,6 +64,10 @@ module RSpec
60
64
 
61
65
  private
62
66
 
67
+ def describable
68
+ @describable ||= DefaultDescribable.new(@message)
69
+ end
70
+
63
71
  def warn_if_any_instance(expression, subject)
64
72
  return unless AnyInstance::Proxy === subject
65
73
 
@@ -100,6 +108,22 @@ module RSpec
100
108
  last.block ||= block
101
109
  nil
102
110
  end
111
+
112
+ # MessageExpectation objects are able to describe themselves in detail.
113
+ # We use this as a fall back when a MessageExpectation is not available.
114
+ # @private
115
+ class DefaultDescribable
116
+ def initialize(message)
117
+ @message = message
118
+ end
119
+
120
+ # This is much simpler for the `any_instance` case than what the
121
+ # user may want, but I'm not up for putting a bunch of effort
122
+ # into full descriptions for `any_instance` expectations at this point :(.
123
+ def description_for(verb)
124
+ "#{verb} #{@message}"
125
+ end
126
+ end
103
127
  end
104
128
  end
105
129
  end
@@ -22,6 +22,10 @@ module RSpec
22
22
  "receive_message_chain"
23
23
  end
24
24
 
25
+ def description
26
+ "receive message chain #{formatted_chain}"
27
+ end
28
+
25
29
  def setup_allowance(subject, &block)
26
30
  chain = StubChain.stub_chain_on(subject, *@chain, &(@block || block))
27
31
  replay_customizations(chain)
@@ -60,6 +64,16 @@ module RSpec
60
64
  customization.playback_onto(chain)
61
65
  end
62
66
  end
67
+
68
+ def formatted_chain
69
+ @formatted_chain ||= @chain.map do |part|
70
+ if Hash === part
71
+ part.keys.first.to_s
72
+ else
73
+ part.to_s
74
+ end
75
+ end.join(".")
76
+ end
63
77
  end
64
78
  end
65
79
  end
@@ -12,6 +12,10 @@ module RSpec
12
12
  "receive_messages"
13
13
  end
14
14
 
15
+ def description
16
+ "receive messages: #{@message_return_value_hash.inspect}"
17
+ end
18
+
15
19
  def setup_expectation(subject)
16
20
  warn_about_block if block_given?
17
21
  each_message_on(proxy_on(subject)) do |host, message, return_value|
@@ -34,47 +34,11 @@ module RSpec
34
34
  end
35
35
  end
36
36
 
37
- # @private
37
+ # Represents an individual method stub or message expectation. The methods
38
+ # defined here can be used to configure how it behaves. The methods return
39
+ # `self` so that they can be chained together to form a fluent interface.
38
40
  class MessageExpectation
39
- # @private
40
- attr_accessor :error_generator, :implementation
41
- attr_reader :message
42
- attr_reader :orig_object
43
- attr_writer :expected_received_count, :expected_from, :argument_list_matcher
44
- protected :expected_received_count=, :expected_from=, :error_generator, :error_generator=, :implementation=
45
-
46
- # rubocop:disable Style/ParameterLists
47
- # @private
48
- def initialize(error_generator, expectation_ordering, expected_from, method_double,
49
- type=:expectation, opts={}, &implementation_block)
50
- @error_generator = error_generator
51
- @error_generator.opts = opts
52
- @expected_from = expected_from
53
- @method_double = method_double
54
- @orig_object = @method_double.object
55
- @message = @method_double.method_name
56
- @actual_received_count = 0
57
- @expected_received_count = type == :expectation ? 1 : :any
58
- @argument_list_matcher = ArgumentListMatcher::MATCH_ALL
59
- @order_group = expectation_ordering
60
- @order_group.register(self) unless type == :stub
61
- @expectation_type = type
62
- @ordered = false
63
- @at_least = @at_most = @exactly = nil
64
- @args_to_yield = []
65
- @failed_fast = nil
66
- @eval_context = nil
67
- @yield_receiver_to_implementation_block = false
68
-
69
- @implementation = Implementation.new
70
- self.inner_implementation_action = implementation_block
71
- end
72
- # rubocop:enable Style/ParameterLists
73
-
74
- # @private
75
- def expected_args
76
- @argument_list_matcher.expected_args
77
- end
41
+ # @!group Configuring Responses
78
42
 
79
43
  # @overload and_return(value)
80
44
  # @overload and_return(first_value, second_value)
@@ -87,8 +51,8 @@ module RSpec
87
51
  # If the message is received more times than there are values, the last
88
52
  # value is received for every subsequent call.
89
53
  #
54
+ # @return [nil] No further chaining is supported after this.
90
55
  # @example
91
- #
92
56
  # allow(counter).to receive(:count).and_return(1)
93
57
  # counter.count # => 1
94
58
  # counter.count # => 1
@@ -101,6 +65,7 @@ module RSpec
101
65
  # counter.count # => 3
102
66
  # # etc
103
67
  def and_return(first_value, *values)
68
+ raise_already_invoked_error_if_necessary(__method__)
104
69
  if negative?
105
70
  raise "`and_return` is not supported with negative message expectations"
106
71
  end
@@ -116,22 +81,13 @@ module RSpec
116
81
  nil
117
82
  end
118
83
 
119
- def and_yield_receiver_to_implementation
120
- @yield_receiver_to_implementation_block = true
121
- self
122
- end
123
-
124
- def yield_receiver_to_implementation_block?
125
- @yield_receiver_to_implementation_block
126
- end
127
-
128
84
  # Tells the object to delegate to the original unmodified method
129
85
  # when it receives the message.
130
86
  #
131
87
  # @note This is only available on partial doubles.
132
88
  #
89
+ # @return [nil] No further chaining is supported after this.
133
90
  # @example
134
- #
135
91
  # expect(counter).to receive(:increment).and_call_original
136
92
  # original_count = counter.count
137
93
  # counter.increment
@@ -149,12 +105,11 @@ module RSpec
149
105
  #
150
106
  # @note This is only available on partial doubles.
151
107
  #
108
+ # @return [nil] No further chaining is supported after this.
152
109
  # @example
153
- #
154
110
  # expect(api).to receive(:large_list).and_wrap_original do |original_method, *args, &block|
155
111
  # original_method.call(*args, &block).first(10)
156
112
  # end
157
- #
158
113
  def and_wrap_original(&block)
159
114
  if RSpec::Mocks::TestDouble === @method_double.object
160
115
  @error_generator.raise_only_valid_on_a_partial_double(:and_call_original)
@@ -163,6 +118,8 @@ module RSpec
163
118
  @implementation = AndWrapOriginalImplementation.new(@method_double.original_method, block)
164
119
  @yield_receiver_to_implementation_block = false
165
120
  end
121
+
122
+ nil
166
123
  end
167
124
 
168
125
  # @overload and_raise
@@ -172,8 +129,8 @@ module RSpec
172
129
  #
173
130
  # Tells the object to raise an exception when the message is received.
174
131
  #
132
+ # @return [nil] No further chaining is supported after this.
175
133
  # @note
176
- #
177
134
  # When you pass an exception class, the MessageExpectation will raise
178
135
  # an instance of it, creating it with `exception` and passing `message`
179
136
  # if specified. If the exception class initializer requires more than
@@ -181,12 +138,12 @@ module RSpec
181
138
  # otherwise this method will raise an ArgumentError exception.
182
139
  #
183
140
  # @example
184
- #
185
141
  # allow(car).to receive(:go).and_raise
186
142
  # allow(car).to receive(:go).and_raise(OutOfGas)
187
143
  # allow(car).to receive(:go).and_raise(OutOfGas, "At least 2 oz of gas needed to drive")
188
144
  # allow(car).to receive(:go).and_raise(OutOfGas.new(2, :oz))
189
145
  def and_raise(exception=RuntimeError, message=nil)
146
+ raise_already_invoked_error_if_necessary(__method__)
190
147
  if exception.respond_to?(:exception)
191
148
  exception = message ? exception.exception(message) : exception.exception
192
149
  end
@@ -201,11 +158,12 @@ module RSpec
201
158
  # Tells the object to throw a symbol (with the object if that form is
202
159
  # used) when the message is received.
203
160
  #
161
+ # @return [nil] No further chaining is supported after this.
204
162
  # @example
205
- #
206
163
  # allow(car).to receive(:go).and_throw(:out_of_gas)
207
164
  # allow(car).to receive(:go).and_throw(:out_of_gas, :level => 0.1)
208
165
  def and_throw(*args)
166
+ raise_already_invoked_error_if_necessary(__method__)
209
167
  self.terminal_implementation_action = Proc.new { throw(*args) }
210
168
  nil
211
169
  end
@@ -213,167 +171,28 @@ module RSpec
213
171
  # Tells the object to yield one or more args to a block when the message
214
172
  # is received.
215
173
  #
174
+ # @return [MessageExpecation] self, to support further chaining.
216
175
  # @example
217
- #
218
176
  # stream.stub(:open).and_yield(StringIO.new)
219
177
  def and_yield(*args, &block)
178
+ raise_already_invoked_error_if_necessary(__method__)
220
179
  yield @eval_context = Object.new if block
221
180
  @args_to_yield << args
222
181
  self.initial_implementation_action = AndYieldImplementation.new(@args_to_yield, @eval_context, @error_generator)
223
182
  self
224
183
  end
184
+ # @!endgroup
225
185
 
226
- # @private
227
- def matches?(message, *args)
228
- @message == message && @argument_list_matcher.args_match?(*args)
229
- end
230
-
231
- # @private
232
- def safe_invoke(parent_stub, *args, &block)
233
- invoke_incrementing_actual_calls_by(1, false, parent_stub, *args, &block)
234
- end
235
-
236
- # @private
237
- def invoke(parent_stub, *args, &block)
238
- invoke_incrementing_actual_calls_by(1, true, parent_stub, *args, &block)
239
- end
240
-
241
- # @private
242
- def invoke_without_incrementing_received_count(parent_stub, *args, &block)
243
- invoke_incrementing_actual_calls_by(0, true, parent_stub, *args, &block)
244
- end
245
-
246
- # @private
247
- def negative?
248
- @expected_received_count == 0 && !@at_least
249
- end
250
-
251
- # @private
252
- def called_max_times?
253
- @expected_received_count != :any &&
254
- !@at_least &&
255
- @expected_received_count > 0 &&
256
- @actual_received_count >= @expected_received_count
257
- end
258
-
259
- # @private
260
- def matches_name_but_not_args(message, *args)
261
- @message == message && !@argument_list_matcher.args_match?(*args)
262
- end
263
-
264
- # @private
265
- def verify_messages_received
266
- InsertOntoBacktrace.line(@expected_from) do
267
- generate_error unless expected_messages_received? || failed_fast?
268
- end
269
- end
270
-
271
- # @private
272
- def expected_messages_received?
273
- ignoring_args? || matches_exact_count? || matches_at_least_count? || matches_at_most_count?
274
- end
275
-
276
- def ensure_expected_ordering_received!
277
- @order_group.verify_invocation_order(self) if @ordered
278
- true
279
- end
280
-
281
- # @private
282
- def ignoring_args?
283
- @expected_received_count == :any
284
- end
285
-
286
- # @private
287
- def matches_at_least_count?
288
- @at_least && @actual_received_count >= @expected_received_count
289
- end
290
-
291
- # @private
292
- def matches_at_most_count?
293
- @at_most && @actual_received_count <= @expected_received_count
294
- end
295
-
296
- # @private
297
- def matches_exact_count?
298
- @expected_received_count == @actual_received_count
299
- end
300
-
301
- # @private
302
- def similar_messages
303
- @similar_messages ||= []
304
- end
305
-
306
- # @private
307
- def advise(*args)
308
- similar_messages << args
309
- end
310
-
311
- # @private
312
- def generate_error
313
- if similar_messages.empty?
314
- @error_generator.raise_expectation_error(@message, @expected_received_count, @argument_list_matcher, @actual_received_count, expectation_count_type, *expected_args)
315
- else
316
- @error_generator.raise_similar_message_args_error(self, *@similar_messages)
317
- end
318
- end
319
-
320
- def expectation_count_type
321
- return :at_least if @at_least
322
- return :at_most if @at_most
323
- nil
324
- end
325
-
326
- # @private
327
- def description
328
- @error_generator.describe_expectation(@message, @expected_received_count, @actual_received_count, *expected_args)
329
- end
330
-
331
- def raise_out_of_order_error
332
- @error_generator.raise_out_of_order_error @message
333
- end
334
-
335
- # Constrains a stub or message expectation to invocations with specific
336
- # arguments.
337
- #
338
- # With a stub, if the message might be received with other args as well,
339
- # you should stub a default value first, and then stub or mock the same
340
- # message using `with` to constrain to specific arguments.
341
- #
342
- # A message expectation will fail if the message is received with different
343
- # arguments.
344
- #
345
- # @example
346
- #
347
- # allow(cart).to receive(:add) { :failure }
348
- # allow(cart).to receive(:add).with(Book.new(:isbn => 1934356379)) { :success }
349
- # cart.add(Book.new(:isbn => 1234567890))
350
- # # => :failure
351
- # cart.add(Book.new(:isbn => 1934356379))
352
- # # => :success
353
- #
354
- # expect(cart).to receive(:add).with(Book.new(:isbn => 1934356379)) { :success }
355
- # cart.add(Book.new(:isbn => 1234567890))
356
- # # => failed expectation
357
- # cart.add(Book.new(:isbn => 1934356379))
358
- # # => passes
359
- def with(*args, &block)
360
- if args.empty?
361
- raise ArgumentError,
362
- "`with` must have at least one argument. Use `no_args` matcher to set the expectation of receiving no arguments."
363
- end
364
-
365
- self.inner_implementation_action = block
366
- @argument_list_matcher = ArgumentListMatcher.new(*args)
367
- self
368
- end
186
+ # @!group Constraining Receive Counts
369
187
 
370
188
  # Constrain a message expectation to be received a specific number of
371
189
  # times.
372
190
  #
191
+ # @return [MessageExpecation] self, to support further chaining.
373
192
  # @example
374
- #
375
193
  # expect(dealer).to receive(:deal_card).exactly(10).times
376
194
  def exactly(n, &block)
195
+ raise_already_invoked_error_if_necessary(__method__)
377
196
  self.inner_implementation_action = block
378
197
  set_expected_received_count :exactly, n
379
198
  self
@@ -382,10 +201,11 @@ module RSpec
382
201
  # Constrain a message expectation to be received at least a specific
383
202
  # number of times.
384
203
  #
204
+ # @return [MessageExpecation] self, to support further chaining.
385
205
  # @example
386
- #
387
206
  # expect(dealer).to receive(:deal_card).at_least(9).times
388
207
  def at_least(n, &block)
208
+ raise_already_invoked_error_if_necessary(__method__)
389
209
  set_expected_received_count :at_least, n
390
210
 
391
211
  if n == 0
@@ -400,10 +220,11 @@ module RSpec
400
220
  # Constrain a message expectation to be received at most a specific
401
221
  # number of times.
402
222
  #
223
+ # @return [MessageExpecation] self, to support further chaining.
403
224
  # @example
404
- #
405
225
  # expect(dealer).to receive(:deal_card).at_most(10).times
406
226
  def at_most(n, &block)
227
+ raise_already_invoked_error_if_necessary(__method__)
407
228
  self.inner_implementation_action = block
408
229
  set_expected_received_count :at_most, n
409
230
  self
@@ -411,8 +232,8 @@ module RSpec
411
232
 
412
233
  # Syntactic sugar for `exactly`, `at_least` and `at_most`
413
234
  #
235
+ # @return [MessageExpecation] self, to support further chaining.
414
236
  # @example
415
- #
416
237
  # expect(dealer).to receive(:deal_card).exactly(10).times
417
238
  # expect(dealer).to receive(:deal_card).at_least(10).times
418
239
  # expect(dealer).to receive(:deal_card).at_most(10).times
@@ -423,8 +244,8 @@ module RSpec
423
244
 
424
245
  # Expect a message not to be received at all.
425
246
  #
247
+ # @return [MessageExpecation] self, to support further chaining.
426
248
  # @example
427
- #
428
249
  # expect(car).to receive(:stop).never
429
250
  def never
430
251
  ErrorGenerator.raise_double_negation_error("expect(obj)") if negative?
@@ -434,8 +255,8 @@ module RSpec
434
255
 
435
256
  # Expect a message to be received exactly one time.
436
257
  #
258
+ # @return [MessageExpecation] self, to support further chaining.
437
259
  # @example
438
- #
439
260
  # expect(car).to receive(:go).once
440
261
  def once(&block)
441
262
  self.inner_implementation_action = block
@@ -445,8 +266,8 @@ module RSpec
445
266
 
446
267
  # Expect a message to be received exactly two times.
447
268
  #
269
+ # @return [MessageExpecation] self, to support further chaining.
448
270
  # @example
449
- #
450
271
  # expect(car).to receive(:go).twice
451
272
  def twice(&block)
452
273
  self.inner_implementation_action = block
@@ -456,19 +277,58 @@ module RSpec
456
277
 
457
278
  # Expect a message to be received exactly three times.
458
279
  #
280
+ # @return [MessageExpecation] self, to support further chaining.
459
281
  # @example
460
- #
461
282
  # expect(car).to receive(:go).thrice
462
283
  def thrice(&block)
463
284
  self.inner_implementation_action = block
464
285
  set_expected_received_count :exactly, 3
465
286
  self
466
287
  end
288
+ # @!endgroup
467
289
 
468
- # Expect messages to be received in a specific order.
290
+ # @!group Other Constraints
291
+
292
+ # Constrains a stub or message expectation to invocations with specific
293
+ # arguments.
294
+ #
295
+ # With a stub, if the message might be received with other args as well,
296
+ # you should stub a default value first, and then stub or mock the same
297
+ # message using `with` to constrain to specific arguments.
298
+ #
299
+ # A message expectation will fail if the message is received with different
300
+ # arguments.
469
301
  #
302
+ # @return [MessageExpecation] self, to support further chaining.
470
303
  # @example
304
+ # allow(cart).to receive(:add) { :failure }
305
+ # allow(cart).to receive(:add).with(Book.new(:isbn => 1934356379)) { :success }
306
+ # cart.add(Book.new(:isbn => 1234567890))
307
+ # # => :failure
308
+ # cart.add(Book.new(:isbn => 1934356379))
309
+ # # => :success
310
+ #
311
+ # expect(cart).to receive(:add).with(Book.new(:isbn => 1934356379)) { :success }
312
+ # cart.add(Book.new(:isbn => 1234567890))
313
+ # # => failed expectation
314
+ # cart.add(Book.new(:isbn => 1934356379))
315
+ # # => passes
316
+ def with(*args, &block)
317
+ raise_already_invoked_error_if_necessary(__method__)
318
+ if args.empty?
319
+ raise ArgumentError,
320
+ "`with` must have at least one argument. Use `no_args` matcher to set the expectation of receiving no arguments."
321
+ end
322
+
323
+ self.inner_implementation_action = block
324
+ @argument_list_matcher = ArgumentListMatcher.new(*args)
325
+ self
326
+ end
327
+
328
+ # Expect messages to be received in a specific order.
471
329
  #
330
+ # @return [MessageExpecation] self, to support further chaining.
331
+ # @example
472
332
  # expect(api).to receive(:prepare).ordered
473
333
  # expect(api).to receive(:run).ordered
474
334
  # expect(api).to receive(:finish).ordered
@@ -482,93 +342,264 @@ module RSpec
482
342
  end
483
343
 
484
344
  # @private
485
- def additional_expected_calls
486
- return 0 if @expectation_type == :stub || !@exactly
487
- @expected_received_count - 1
488
- end
345
+ # Contains the parts of `MessageExpecation` that aren't part of
346
+ # rspec-mocks' public API. The class is very big and could really use
347
+ # some collaborators it delegates to for this stuff but for now this was
348
+ # the simplest way to split the public from private stuff to make it
349
+ # easier to publish the docs for the APIs we want published.
350
+ module ImplementationDetails
351
+ attr_accessor :error_generator, :implementation
352
+ attr_reader :message
353
+ attr_reader :orig_object
354
+ attr_writer :expected_received_count, :expected_from, :argument_list_matcher
355
+ protected :expected_received_count=, :expected_from=, :error_generator, :error_generator=, :implementation=
356
+
357
+ # rubocop:disable Style/ParameterLists
358
+ def initialize(error_generator, expectation_ordering, expected_from, method_double,
359
+ type=:expectation, opts={}, &implementation_block)
360
+ @error_generator = error_generator
361
+ @error_generator.opts = opts
362
+ @expected_from = expected_from
363
+ @method_double = method_double
364
+ @orig_object = @method_double.object
365
+ @message = @method_double.method_name
366
+ @actual_received_count = 0
367
+ @expected_received_count = type == :expectation ? 1 : :any
368
+ @argument_list_matcher = ArgumentListMatcher::MATCH_ALL
369
+ @order_group = expectation_ordering
370
+ @order_group.register(self) unless type == :stub
371
+ @expectation_type = type
372
+ @ordered = false
373
+ @at_least = @at_most = @exactly = nil
374
+ @args_to_yield = []
375
+ @failed_fast = nil
376
+ @eval_context = nil
377
+ @yield_receiver_to_implementation_block = false
489
378
 
490
- # @private
491
- def ordered?
492
- @ordered
493
- end
379
+ @implementation = Implementation.new
380
+ self.inner_implementation_action = implementation_block
381
+ end
382
+ # rubocop:enable Style/ParameterLists
494
383
 
495
- # @private
496
- def negative_expectation_for?(message)
497
- @message == message && negative?
498
- end
384
+ def expected_args
385
+ @argument_list_matcher.expected_args
386
+ end
499
387
 
500
- # @private
501
- def actual_received_count_matters?
502
- @at_least || @at_most || @exactly
503
- end
388
+ def and_yield_receiver_to_implementation
389
+ @yield_receiver_to_implementation_block = true
390
+ self
391
+ end
504
392
 
505
- # @private
506
- def increase_actual_received_count!
507
- @actual_received_count += 1
508
- end
393
+ def yield_receiver_to_implementation_block?
394
+ @yield_receiver_to_implementation_block
395
+ end
509
396
 
510
- private
397
+ def matches?(message, *args)
398
+ @message == message && @argument_list_matcher.args_match?(*args)
399
+ end
400
+
401
+ def safe_invoke(parent_stub, *args, &block)
402
+ invoke_incrementing_actual_calls_by(1, false, parent_stub, *args, &block)
403
+ end
404
+
405
+ def invoke(parent_stub, *args, &block)
406
+ invoke_incrementing_actual_calls_by(1, true, parent_stub, *args, &block)
407
+ end
408
+
409
+ def invoke_without_incrementing_received_count(parent_stub, *args, &block)
410
+ invoke_incrementing_actual_calls_by(0, true, parent_stub, *args, &block)
411
+ end
511
412
 
512
- def invoke_incrementing_actual_calls_by(increment, allowed_to_fail, parent_stub, *args, &block)
513
- args.unshift(orig_object) if yield_receiver_to_implementation_block?
413
+ def negative?
414
+ @expected_received_count == 0 && !@at_least
415
+ end
514
416
 
515
- if negative? || (allowed_to_fail && (@exactly || @at_most) && (@actual_received_count == @expected_received_count))
516
- @actual_received_count += increment
517
- @failed_fast = true
518
- # args are the args we actually received, @argument_list_matcher is the
519
- # list of args we were expecting
520
- @error_generator.raise_expectation_error(@message, @expected_received_count, @argument_list_matcher, @actual_received_count, expectation_count_type, *args)
417
+ def called_max_times?
418
+ @expected_received_count != :any &&
419
+ !@at_least &&
420
+ @expected_received_count > 0 &&
421
+ @actual_received_count >= @expected_received_count
521
422
  end
522
423
 
523
- @order_group.handle_order_constraint self
424
+ def matches_name_but_not_args(message, *args)
425
+ @message == message && !@argument_list_matcher.args_match?(*args)
426
+ end
524
427
 
525
- begin
526
- if implementation.present?
527
- implementation.call(*args, &block)
528
- elsif parent_stub
529
- parent_stub.invoke(nil, *args, &block)
428
+ def verify_messages_received
429
+ InsertOntoBacktrace.line(@expected_from) do
430
+ generate_error unless expected_messages_received? || failed_fast?
530
431
  end
531
- ensure
532
- @actual_received_count += increment
533
432
  end
534
- end
535
433
 
536
- def failed_fast?
537
- @failed_fast
538
- end
434
+ def expected_messages_received?
435
+ ignoring_args? || matches_exact_count? || matches_at_least_count? || matches_at_most_count?
436
+ end
539
437
 
540
- def set_expected_received_count(relativity, n)
541
- @at_least = (relativity == :at_least)
542
- @at_most = (relativity == :at_most)
543
- @exactly = (relativity == :exactly)
544
- @expected_received_count = case n
545
- when Numeric then n
546
- when :once then 1
547
- when :twice then 2
548
- when :thrice then 3
549
- end
550
- end
438
+ def ensure_expected_ordering_received!
439
+ @order_group.verify_invocation_order(self) if @ordered
440
+ true
441
+ end
551
442
 
552
- def initial_implementation_action=(action)
553
- implementation.initial_action = action
554
- end
443
+ def ignoring_args?
444
+ @expected_received_count == :any
445
+ end
555
446
 
556
- def inner_implementation_action=(action)
557
- return unless action
558
- warn_about_stub_override if implementation.inner_action
559
- implementation.inner_action = action
560
- end
447
+ def matches_at_least_count?
448
+ @at_least && @actual_received_count >= @expected_received_count
449
+ end
561
450
 
562
- def terminal_implementation_action=(action)
563
- implementation.terminal_action = action
564
- end
451
+ def matches_at_most_count?
452
+ @at_most && @actual_received_count <= @expected_received_count
453
+ end
454
+
455
+ def matches_exact_count?
456
+ @expected_received_count == @actual_received_count
457
+ end
458
+
459
+ def similar_messages
460
+ @similar_messages ||= []
461
+ end
462
+
463
+ def advise(*args)
464
+ similar_messages << args
465
+ end
466
+
467
+ def generate_error
468
+ if similar_messages.empty?
469
+ @error_generator.raise_expectation_error(@message, @expected_received_count, @argument_list_matcher, @actual_received_count, expectation_count_type, *expected_args)
470
+ else
471
+ @error_generator.raise_similar_message_args_error(self, *@similar_messages)
472
+ end
473
+ end
474
+
475
+ def expectation_count_type
476
+ return :at_least if @at_least
477
+ return :at_most if @at_most
478
+ nil
479
+ end
480
+
481
+ def description_for(verb)
482
+ @error_generator.describe_expectation(
483
+ verb, @message, @expected_received_count,
484
+ @actual_received_count, *expected_args
485
+ )
486
+ end
487
+
488
+ def raise_out_of_order_error
489
+ @error_generator.raise_out_of_order_error @message
490
+ end
491
+
492
+ def additional_expected_calls
493
+ return 0 if @expectation_type == :stub || !@exactly
494
+ @expected_received_count - 1
495
+ end
496
+
497
+ def ordered?
498
+ @ordered
499
+ end
500
+
501
+ def negative_expectation_for?(message)
502
+ @message == message && negative?
503
+ end
504
+
505
+ def actual_received_count_matters?
506
+ @at_least || @at_most || @exactly
507
+ end
565
508
 
566
- def warn_about_stub_override
567
- RSpec.warning(
568
- "You're overriding a previous stub implementation of `#{@message}`. " \
569
- "Called from #{CallerFilter.first_non_rspec_line}."
570
- )
509
+ def increase_actual_received_count!
510
+ @actual_received_count += 1
511
+ end
512
+
513
+ def fail_if_problematic_received_arg_mutations(received_arg_list)
514
+ return if @argument_list_matcher == ArgumentListMatcher::MATCH_ALL
515
+ return unless received_arg_list.has_mutations?
516
+
517
+ raise CannotSupportArgMutationsError,
518
+ "`have_received(...).with(...)` cannot be used when received " \
519
+ "message args have later been mutated. You can use a normal " \
520
+ "message expectation (`expect(...).to receive(...).with(...)`) " \
521
+ "instead."
522
+ end
523
+
524
+ private
525
+
526
+ def invoke_incrementing_actual_calls_by(increment, allowed_to_fail, parent_stub, *args, &block)
527
+ args.unshift(orig_object) if yield_receiver_to_implementation_block?
528
+
529
+ if negative? || (allowed_to_fail && (@exactly || @at_most) && (@actual_received_count == @expected_received_count))
530
+ @actual_received_count += increment
531
+ @failed_fast = true
532
+ # args are the args we actually received, @argument_list_matcher is the
533
+ # list of args we were expecting
534
+ @error_generator.raise_expectation_error(@message, @expected_received_count, @argument_list_matcher, @actual_received_count, expectation_count_type, *args)
535
+ end
536
+
537
+ @order_group.handle_order_constraint self
538
+
539
+ begin
540
+ if implementation.present?
541
+ implementation.call(*args, &block)
542
+ elsif parent_stub
543
+ parent_stub.invoke(nil, *args, &block)
544
+ end
545
+ ensure
546
+ @actual_received_count += increment
547
+ end
548
+ end
549
+
550
+ def has_been_invoked?
551
+ @actual_received_count > 0
552
+ end
553
+
554
+ def raise_already_invoked_error_if_necessary(calling_customization)
555
+ return unless has_been_invoked?
556
+
557
+ error_message = "The message expectation for #{orig_object.inspect}.#{message} has already been invoked " \
558
+ "and cannot be modified further (e.g. using `#{calling_customization}`). All message expectation " \
559
+ "customizations must be applied before it is used for the first time."
560
+
561
+ raise MockExpectationAlreadyInvokedError, error_message
562
+ end
563
+
564
+ def failed_fast?
565
+ @failed_fast
566
+ end
567
+
568
+ def set_expected_received_count(relativity, n)
569
+ @at_least = (relativity == :at_least)
570
+ @at_most = (relativity == :at_most)
571
+ @exactly = (relativity == :exactly)
572
+ @expected_received_count = case n
573
+ when Numeric then n
574
+ when :once then 1
575
+ when :twice then 2
576
+ when :thrice then 3
577
+ end
578
+ end
579
+
580
+ def initial_implementation_action=(action)
581
+ implementation.initial_action = action
582
+ end
583
+
584
+ def inner_implementation_action=(action)
585
+ return unless action
586
+ warn_about_stub_override if implementation.inner_action
587
+ implementation.inner_action = action
588
+ end
589
+
590
+ def terminal_implementation_action=(action)
591
+ implementation.terminal_action = action
592
+ end
593
+
594
+ def warn_about_stub_override
595
+ RSpec.warning(
596
+ "You're overriding a previous stub implementation of `#{@message}`. " \
597
+ "Called from #{CallerFilter.first_non_rspec_line}."
598
+ )
599
+ end
571
600
  end
601
+
602
+ include ImplementationDetails
572
603
  end
573
604
 
574
605
  # Handles the implementation of an `and_yield` declaration.