rspec-mocks 3.0.4 → 3.1.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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/Changelog.md +26 -0
  5. data/README.md +29 -15
  6. data/lib/rspec/mocks/any_instance/chain.rb +2 -2
  7. data/lib/rspec/mocks/any_instance/expectation_chain.rb +2 -3
  8. data/lib/rspec/mocks/any_instance/message_chains.rb +8 -7
  9. data/lib/rspec/mocks/any_instance/proxy.rb +2 -2
  10. data/lib/rspec/mocks/any_instance/recorder.rb +27 -25
  11. data/lib/rspec/mocks/any_instance/stub_chain.rb +3 -5
  12. data/lib/rspec/mocks/argument_list_matcher.rb +4 -4
  13. data/lib/rspec/mocks/argument_matchers.rb +23 -5
  14. data/lib/rspec/mocks/configuration.rb +3 -10
  15. data/lib/rspec/mocks/error_generator.rb +33 -27
  16. data/lib/rspec/mocks/example_methods.rb +74 -6
  17. data/lib/rspec/mocks/instance_method_stasher.rb +5 -5
  18. data/lib/rspec/mocks/matchers/have_received.rb +7 -8
  19. data/lib/rspec/mocks/matchers/receive.rb +8 -8
  20. data/lib/rspec/mocks/matchers/receive_message_chain.rb +4 -5
  21. data/lib/rspec/mocks/matchers/receive_messages.rb +6 -7
  22. data/lib/rspec/mocks/message_chain.rb +5 -5
  23. data/lib/rspec/mocks/message_expectation.rb +56 -28
  24. data/lib/rspec/mocks/method_double.rb +15 -12
  25. data/lib/rspec/mocks/method_reference.rb +8 -7
  26. data/lib/rspec/mocks/mutate_const.rb +26 -100
  27. data/lib/rspec/mocks/object_reference.rb +12 -13
  28. data/lib/rspec/mocks/order_group.rb +4 -5
  29. data/lib/rspec/mocks/proxy.rb +31 -25
  30. data/lib/rspec/mocks/space.rb +24 -24
  31. data/lib/rspec/mocks/syntax.rb +8 -8
  32. data/lib/rspec/mocks/targets.rb +6 -6
  33. data/lib/rspec/mocks/test_double.rb +3 -3
  34. data/lib/rspec/mocks/verifying_double.rb +10 -12
  35. data/lib/rspec/mocks/verifying_message_expecation.rb +15 -13
  36. data/lib/rspec/mocks/verifying_proxy.rb +11 -15
  37. data/lib/rspec/mocks/version.rb +1 -1
  38. metadata +5 -5
  39. metadata.gz.sig +0 -0
@@ -40,29 +40,38 @@ module RSpec
40
40
  def raise_unexpected_message_args_error(expectation, *args)
41
41
  expected_args = format_args(*expectation.expected_args)
42
42
  actual_args = format_received_args(*args)
43
- __raise "#{intro} received #{expectation.message.inspect} with unexpected arguments\n expected: #{expected_args}\n got: #{actual_args}"
43
+ __raise "#{intro} received #{expectation.message.inspect} with " \
44
+ "unexpected arguments\n expected: #{expected_args}\n" \
45
+ " got: #{actual_args}"
44
46
  end
45
47
 
46
48
  # @private
47
49
  def raise_missing_default_stub_error(expectation, *args)
48
50
  expected_args = format_args(*expectation.expected_args)
49
51
  actual_args = format_received_args(*args)
50
- __raise "#{intro} received #{expectation.message.inspect} with unexpected arguments\n expected: #{expected_args}\n got: #{actual_args}\n Please stub a default value first if message might be received with other args as well. \n"
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"
51
56
  end
52
57
 
53
58
  # @private
54
59
  def raise_similar_message_args_error(expectation, *args_for_multiple_calls)
55
60
  expected_args = format_args(*expectation.expected_args)
56
- actual_args = args_for_multiple_calls.collect {|a| format_received_args(*a)}.join(", ")
57
- __raise "#{intro} received #{expectation.message.inspect} with unexpected arguments\n expected: #{expected_args}\n got: #{actual_args}"
61
+ 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}"
58
65
  end
59
66
 
67
+ # rubocop:disable Style/ParameterLists
60
68
  # @private
61
69
  def raise_expectation_error(message, expected_received_count, argument_list_matcher, actual_received_count, expectation_count_type, *args)
62
70
  expected_part = expected_part_of_expectation_error(expected_received_count, expectation_count_type, argument_list_matcher)
63
71
  received_part = received_part_of_expectation_error(actual_received_count, *args)
64
72
  __raise "(#{intro}).#{message}#{format_args(*args)}\n #{expected_part}\n #{received_part}"
65
73
  end
74
+ # rubocop:enable Style/ParameterLists
66
75
 
67
76
  # @private
68
77
  def raise_unimplemented_error(doubled_module, method_name)
@@ -87,10 +96,10 @@ module RSpec
87
96
  # @private
88
97
  def raise_expired_test_double_error
89
98
  raise ExpiredTestDoubleError,
90
- "#{intro} was originally created in one example but has leaked into " +
91
- "another example and can no longer be used. rspec-mocks' doubles are " +
92
- "designed to only last for one example, and you need to create a new " +
93
- "one in each example you wish to use it for."
99
+ "#{intro} was originally created in one example but has leaked into " \
100
+ "another example and can no longer be used. rspec-mocks' doubles are " \
101
+ "designed to only last for one example, and you need to create a new " \
102
+ "one in each example you wish to use it for."
94
103
  end
95
104
 
96
105
  # @private
@@ -128,15 +137,13 @@ module RSpec
128
137
  # @private
129
138
  def method_call_args_description(args)
130
139
  case args.first
131
- when ArgumentMatchers::AnyArgsMatcher
132
- return " with any arguments"
133
- when ArgumentMatchers::NoArgsMatcher
134
- return " with no arguments"
140
+ when ArgumentMatchers::AnyArgsMatcher then " with any arguments"
141
+ when ArgumentMatchers::NoArgsMatcher then " with no arguments"
135
142
  end
136
143
  end
137
144
 
138
145
  # @private
139
- def describe_expectation(message, expected_received_count, actual_received_count, *args)
146
+ def describe_expectation(message, expected_received_count, _actual_received_count, *args)
140
147
  "have received #{message}#{format_args(*args)} #{count_message(expected_received_count)}"
141
148
  end
142
149
 
@@ -162,30 +169,30 @@ module RSpec
162
169
 
163
170
  # @private
164
171
  def raise_only_valid_on_a_partial_double(method)
165
- __raise "#{intro} is a pure test double. `#{method}` is only " +
172
+ __raise "#{intro} is a pure test double. `#{method}` is only " \
166
173
  "available on a partial double."
167
174
  end
168
175
 
169
176
  # @private
170
177
  def raise_expectation_on_unstubbed_method(method)
171
- __raise "#{intro} expected to have received #{method}, but that " +
172
- "method has not been stubbed."
178
+ __raise "#{intro} expected to have received #{method}, but that " \
179
+ "object is not a spy or method has not been stubbed."
173
180
  end
174
181
 
175
182
  # @private
176
183
  def raise_expectation_on_mocked_method(method)
177
- __raise "#{intro} expected to have received #{method}, but that " +
178
- "method has been mocked instead of stubbed."
184
+ __raise "#{intro} expected to have received #{method}, but that " \
185
+ "method has been mocked instead of stubbed or spied."
179
186
  end
180
187
 
181
188
  def self.raise_double_negation_error(wrapped_expression)
182
- raise "Isn't life confusing enough? You've already set a " +
183
- "negative message expectation and now you are trying to " +
184
- "negate it again with `never`. What does an expression like " +
189
+ raise "Isn't life confusing enough? You've already set a " \
190
+ "negative message expectation and now you are trying to " \
191
+ "negate it again with `never`. What does an expression like " \
185
192
  "`#{wrapped_expression}.not_to receive(:msg).never` even mean?"
186
193
  end
187
194
 
188
- private
195
+ private
189
196
 
190
197
  def intro
191
198
  if @name
@@ -203,7 +210,7 @@ module RSpec
203
210
 
204
211
  def __raise(message)
205
212
  message = opts[:message] unless opts[:message].nil?
206
- Kernel::raise(RSpec::Mocks::MockExpectationError, message)
213
+ Kernel.raise(RSpec::Mocks::MockExpectationError, message)
207
214
  end
208
215
 
209
216
  def arg_message(*args)
@@ -215,7 +222,7 @@ module RSpec
215
222
  end
216
223
 
217
224
  def arg_list(*args)
218
- args.collect {|arg| arg_has_valid_description(arg) ? arg.description : arg.inspect }.join(", ")
225
+ args.map { |arg| arg_has_valid_description(arg) ? arg.description : arg.inspect }.join(", ")
219
226
  end
220
227
 
221
228
  def arg_has_valid_description(arg)
@@ -229,19 +236,18 @@ module RSpec
229
236
  end
230
237
 
231
238
  def received_arg_list(*args)
232
- args.collect(&:inspect).join(", ")
239
+ args.map(&:inspect).join(", ")
233
240
  end
234
241
 
235
242
  def count_message(count, expectation_count_type=nil)
236
243
  return "at least #{times(count.abs)}" if count < 0 || expectation_count_type == :at_least
237
244
  return "at most #{times(count)}" if expectation_count_type == :at_most
238
- return times(count)
245
+ times(count)
239
246
  end
240
247
 
241
248
  def times(count)
242
249
  "#{count} time#{count == 1 ? '' : 's'}"
243
250
  end
244
-
245
251
  end
246
252
  end
247
253
  end
@@ -84,6 +84,73 @@ module RSpec
84
84
  ExampleMethods.declare_verifying_double(ObjectVerifyingDouble, ref, *args)
85
85
  end
86
86
 
87
+ # @overload spy()
88
+ # @overload spy(name)
89
+ # @param name [String/Symbol] used to clarify intent
90
+ # @overload spy(stubs)
91
+ # @param stubs (Hash) hash of message/return-value pairs
92
+ # @overload spy(name, stubs)
93
+ # @param name [String/Symbol] used to clarify intent
94
+ # @param stubs (Hash) hash of message/return-value pairs
95
+ # @return (Double)
96
+ #
97
+ # Constructs a test double that is optimized for use with
98
+ # `have_received`. With a normal double one has to stub methods in order
99
+ # to be able to spy them. A spy automatically spies on all methods.
100
+ def spy(*args)
101
+ double(*args).as_null_object
102
+ end
103
+
104
+ # @overload instance_spy(doubled_class)
105
+ # @param doubled_class [String, Class]
106
+ # @overload instance_spy(doubled_class, stubs)
107
+ # @param doubled_class [String, Class]
108
+ # @param stubs [Hash] hash of message/return-value pairs
109
+ # @return InstanceVerifyingDouble
110
+ #
111
+ # Constructs a test double that is optimized for use with `have_received`
112
+ # against a specific class. If the given class name has been loaded, only
113
+ # instance methods defined on the class are allowed to be stubbed. With
114
+ # a normal double one has to stub methods in order to be able to spy
115
+ # them. An instance_spy automatically spies on all instance methods to
116
+ # which the class responds.
117
+ def instance_spy(*args)
118
+ instance_double(*args).as_null_object
119
+ end
120
+
121
+ # @overload object_spy(object_or_name)
122
+ # @param object_or_name [String, Object]
123
+ # @overload object_spy(object_or_name, stubs)
124
+ # @param object_or_name [String, Object]
125
+ # @param stubs [Hash] hash of message/return-value pairs
126
+ # @return ObjectVerifyingDouble
127
+ #
128
+ # Constructs a test double that is optimized for use with `have_received`
129
+ # against a specific object. Only instance methods defined on the object
130
+ # are allowed to be stubbed. With a normal double one has to stub
131
+ # methods in order to be able to spy them. An object_spy automatically
132
+ # spies on all methods to which the object responds.
133
+ def object_spy(*args)
134
+ object_double(*args).as_null_object
135
+ end
136
+
137
+ # @overload class_spy(doubled_class)
138
+ # @param doubled_class [String, Module]
139
+ # @overload class_spy(doubled_class, stubs)
140
+ # @param doubled_class [String, Module]
141
+ # @param stubs [Hash] hash of message/return-value pairs
142
+ # @return ClassVerifyingDouble
143
+ #
144
+ # Constructs a test double that is optimized for use with `have_received`
145
+ # against a specific class. If the given class name has been loaded,
146
+ # only class methods defined on the class are allowed to be stubbed.
147
+ # With a normal double one has to stub methods in order to be able to spy
148
+ # them. An class_spy automatically spies on all class methods to which the
149
+ # class responds.
150
+ def class_spy(*args)
151
+ class_double(*args).as_null_object
152
+ end
153
+
87
154
  # Disables warning messages about expectations being set on nil.
88
155
  #
89
156
  # By default warning messages are issued when expectations are set on
@@ -130,7 +197,7 @@ module RSpec
130
197
  # stub_const("CardDeck", Class.new, :transfer_nested_constants => [:SUITS])
131
198
  # CardDeck::SUITS # => our suits array
132
199
  # CardDeck::NUM_CARDS # => uninitialized constant error
133
- def stub_const(constant_name, value, options = {})
200
+ def stub_const(constant_name, value, options={})
134
201
  ConstantMutator.stub(constant_name, value, options)
135
202
  end
136
203
 
@@ -151,8 +218,9 @@ module RSpec
151
218
  end
152
219
 
153
220
  # 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.
221
+ # course of the test. On a spy objects or as null object doubles this
222
+ # works for any method, on other objects the method must have
223
+ # been stubbed beforehand in order for messages to be verified.
156
224
  #
157
225
  # Stubbing and verifying messages received in this way implements the
158
226
  # Test Spy pattern.
@@ -288,9 +356,9 @@ module RSpec
288
356
  !ref.defined?
289
357
 
290
358
  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."
359
+ "#{ref.description} is not a defined constant. " \
360
+ "Perhaps you misspelt it? " \
361
+ "Disable check with verify_doubled_constant_names configuration option."
294
362
  end
295
363
 
296
364
  declare_double(type, ref, *args)
@@ -53,7 +53,7 @@ module RSpec
53
53
 
54
54
  # @private
55
55
  def stash
56
- return if !method_defined_directly_on_klass?
56
+ return unless method_defined_directly_on_klass?
57
57
  @original_method ||= ::RSpec::Support.method_handle_for(@object, @method)
58
58
  end
59
59
 
@@ -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
 
@@ -3,9 +3,9 @@ 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
+ COUNT_CONSTRAINTS = %w[exactly at_least at_most times once twice thrice]
7
+ ARGS_CONSTRAINTS = %w[with]
8
+ CONSTRAINTS = COUNT_CONSTRAINTS + ARGS_CONSTRAINTS + %w[ordered]
9
9
 
10
10
  def initialize(method_name, &block)
11
11
  @method_name = method_name
@@ -71,14 +71,13 @@ module RSpec
71
71
  end
72
72
 
73
73
  def ensure_count_unconstrained
74
- if count_constraint
75
- raise RSpec::Mocks::MockExpectationError,
76
- "can't use #{count_constraint} when negative"
77
- end
74
+ return unless count_constraint
75
+ raise RSpec::Mocks::MockExpectationError,
76
+ "can't use #{count_constraint} when negative"
78
77
  end
79
78
 
80
79
  def count_constraint
81
- @constraints.map(&:first).detect do |constraint|
80
+ @constraints.map(&:first).find do |constraint|
82
81
  COUNT_CONSTRAINTS.include?(constraint)
83
82
  end
84
83
  end
@@ -61,14 +61,14 @@ module RSpec
61
61
  private
62
62
 
63
63
  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
64
+ return unless AnyInstance::Proxy === subject
65
+
66
+ RSpec.warning(
67
+ "`#{expression}(#{subject.klass}.any_instance).to` " \
68
+ "is probably not what you meant, it does not operate on " \
69
+ "any instance of `#{subject.klass}`. " \
70
+ "Use `#{expression}_any_instance_of(#{subject.klass}).to` instead."
71
+ )
72
72
  end
73
73
 
74
74
  def setup_mock_proxy_method_substitute(subject, method, block)
@@ -44,11 +44,10 @@ module RSpec
44
44
  replay_customizations(chain)
45
45
  end
46
46
 
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
- )
47
+ def setup_negative_expectation(*_args)
48
+ raise NegationUnsupportedError,
49
+ "`expect(...).not_to receive_message_chain` is not supported " \
50
+ "since it doesn't really make sense. What would it even mean?"
52
51
  end
53
52
 
54
53
  alias matches? setup_expectation
@@ -3,7 +3,6 @@ module RSpec
3
3
  module Matchers
4
4
  # @private
5
5
  class ReceiveMessages
6
-
7
6
  def initialize(message_return_value_hash)
8
7
  @message_return_value_hash = message_return_value_hash
9
8
  @backtrace_line = CallerFilter.first_non_rspec_line
@@ -15,29 +14,29 @@ module RSpec
15
14
 
16
15
  def setup_expectation(subject)
17
16
  warn_about_block if block_given?
18
- each_message_on( proxy_on(subject) ) do |host, message, return_value|
17
+ each_message_on(proxy_on(subject)) do |host, message, return_value|
19
18
  host.add_simple_expectation(message, return_value, @backtrace_line)
20
19
  end
21
20
  end
22
21
  alias matches? setup_expectation
23
22
 
24
- def setup_negative_expectation(subject)
23
+ def setup_negative_expectation(_subject)
25
24
  raise NegationUnsupportedError,
26
- "`expect(...).to_not receive_messages` is not supported since it " +
27
- "doesn't really make sense. What would it even mean?"
25
+ "`expect(...).to_not receive_messages` is not supported since it " \
26
+ "doesn't really make sense. What would it even mean?"
28
27
  end
29
28
  alias does_not_match? setup_negative_expectation
30
29
 
31
30
  def setup_allowance(subject)
32
31
  warn_about_block if block_given?
33
- each_message_on( proxy_on(subject) ) do |host, message, return_value|
32
+ each_message_on(proxy_on(subject)) do |host, message, return_value|
34
33
  host.add_simple_stub(message, return_value)
35
34
  end
36
35
  end
37
36
 
38
37
  def setup_any_instance_expectation(subject)
39
38
  warn_about_block if block_given?
40
- each_message_on( any_instance_of(subject) ) do |host, message, return_value|
39
+ each_message_on(any_instance_of(subject)) do |host, message, return_value|
41
40
  host.should_receive(message).and_return(return_value)
42
41
  end
43
42
  end
@@ -12,10 +12,10 @@ module RSpec
12
12
  # @api private
13
13
  def setup_chain
14
14
  if chain.length > 1
15
- if matching_stub = find_matching_stub
15
+ if (matching_stub = find_matching_stub)
16
16
  chain.shift
17
17
  chain_on(matching_stub.invoke(nil), *chain, &@block)
18
- elsif matching_expectation = find_matching_expectation
18
+ elsif (matching_expectation = find_matching_expectation)
19
19
  chain.shift
20
20
  chain_on(matching_expectation.invoke_without_incrementing_received_count(nil), *chain, &@block)
21
21
  else
@@ -30,8 +30,8 @@ module RSpec
30
30
 
31
31
  private
32
32
 
33
- def expectation(object, message, &return_block)
34
- raise NotImplementedError.new
33
+ def expectation(_object, _message, &_return_block)
34
+ raise NotImplementedError
35
35
  end
36
36
 
37
37
  def chain_on(object, *chain, &block)
@@ -42,7 +42,7 @@ module RSpec
42
42
  def format_chain(*chain, &blk)
43
43
  if Hash === chain.last
44
44
  hash = chain.pop
45
- hash.each do |k,v|
45
+ hash.each do |k, v|
46
46
  chain << k
47
47
  blk = Proc.new { v }
48
48
  end
@@ -1,6 +1,5 @@
1
1
  module RSpec
2
2
  module Mocks
3
-
4
3
  # A message expectation that only allows concrete return values to be set
5
4
  # for a message. While this same effect can be achieved using a standard
6
5
  # MessageExpecation, this version is much faster and so can be used as an
@@ -8,8 +7,7 @@ module RSpec
8
7
  #
9
8
  # @private
10
9
  class SimpleMessageExpectation
11
-
12
- def initialize(message, response, error_generator, backtrace_line = nil)
10
+ def initialize(message, response, error_generator, backtrace_line=nil)
13
11
  @message, @response, @error_generator, @backtrace_line = message.to_sym, response, error_generator, backtrace_line
14
12
  @received = false
15
13
  end
@@ -45,6 +43,7 @@ module RSpec
45
43
  attr_writer :expected_received_count, :expected_from, :argument_list_matcher
46
44
  protected :expected_received_count=, :expected_from=, :error_generator, :error_generator=, :implementation=
47
45
 
46
+ # rubocop:disable Style/ParameterLists
48
47
  # @private
49
48
  def initialize(error_generator, expectation_ordering, expected_from, method_double,
50
49
  type=:expectation, opts={}, &implementation_block)
@@ -70,6 +69,7 @@ module RSpec
70
69
  @implementation = Implementation.new
71
70
  self.inner_implementation_action = implementation_block
72
71
  end
72
+ # rubocop:enable Style/ParameterLists
73
73
 
74
74
  # @private
75
75
  def expected_args
@@ -110,7 +110,7 @@ module RSpec
110
110
  end
111
111
 
112
112
  values.unshift(first_value)
113
- @expected_received_count = [@expected_received_count, values.size].max unless ignoring_args? || (@expected_received_count == 0 and @at_least)
113
+ @expected_received_count = [@expected_received_count, values.size].max unless ignoring_args? || (@expected_received_count == 0 && @at_least)
114
114
  self.terminal_implementation_action = AndReturnImplementation.new(values)
115
115
 
116
116
  nil
@@ -137,11 +137,30 @@ module RSpec
137
137
  # counter.increment
138
138
  # expect(counter.count).to eq(original_count + 1)
139
139
  def and_call_original
140
+ and_wrap_original do |original, *args, &block|
141
+ original.call(*args, &block)
142
+ end
143
+ end
144
+
145
+ # Decorates the stubbed method with the supplied block. The original
146
+ # unmodified method is passed to the block along with any method call
147
+ # arguments so you can delegate to it, whilst still being able to
148
+ # change what args are passed to it and/or change the return value.
149
+ #
150
+ # @note This is only available on partial doubles.
151
+ #
152
+ # @example
153
+ #
154
+ # expect(api).to receive(:large_list).and_wrap_original do |original_method, *args, &block|
155
+ # original_method.call(*args, &block).first(10)
156
+ # end
157
+ #
158
+ def and_wrap_original(&block)
140
159
  if RSpec::Mocks::TestDouble === @method_double.object
141
160
  @error_generator.raise_only_valid_on_a_partial_double(:and_call_original)
142
161
  else
143
162
  warn_about_stub_override if implementation.inner_action
144
- @implementation = AndCallOriginalImplementation.new(@method_double.original_method)
163
+ @implementation = AndWrapOriginalImplementation.new(@method_double.original_method, block)
145
164
  @yield_receiver_to_implementation_block = false
146
165
  end
147
166
  end
@@ -167,7 +186,7 @@ module RSpec
167
186
  # allow(car).to receive(:go).and_raise(OutOfGas)
168
187
  # allow(car).to receive(:go).and_raise(OutOfGas, "At least 2 oz of gas needed to drive")
169
188
  # allow(car).to receive(:go).and_raise(OutOfGas.new(2, :oz))
170
- def and_raise(exception = RuntimeError, message = nil)
189
+ def and_raise(exception=RuntimeError, message=nil)
171
190
  if exception.respond_to?(:exception)
172
191
  exception = message ? exception.exception(message) : exception.exception
173
192
  end
@@ -234,7 +253,7 @@ module RSpec
234
253
 
235
254
  # @private
236
255
  def matches_name_but_not_args(message, *args)
237
- @message == message and not @argument_list_matcher.args_match?(*args)
256
+ @message == message && !@argument_list_matcher.args_match?(*args)
238
257
  end
239
258
 
240
259
  # @private
@@ -296,7 +315,7 @@ module RSpec
296
315
  def expectation_count_type
297
316
  return :at_least if @at_least
298
317
  return :at_most if @at_most
299
- return nil
318
+ nil
300
319
  end
301
320
 
302
321
  # @private
@@ -335,7 +354,7 @@ module RSpec
335
354
  def with(*args, &block)
336
355
  if args.empty?
337
356
  raise ArgumentError,
338
- "`with` must have at least one argument. Use `no_args` matcher to set the expectation of receiving no arguments."
357
+ "`with` must have at least one argument. Use `no_args` matcher to set the expectation of receiving no arguments."
339
358
  end
340
359
 
341
360
  self.inner_implementation_action = block
@@ -430,6 +449,17 @@ module RSpec
430
449
  self
431
450
  end
432
451
 
452
+ # Expect a message to be received exactly three times.
453
+ #
454
+ # @example
455
+ #
456
+ # expect(car).to receive(:go).thrice
457
+ def thrice(&block)
458
+ self.inner_implementation_action = block
459
+ set_expected_received_count :exactly, 3
460
+ self
461
+ end
462
+
433
463
  # Expect messages to be received in a specific order.
434
464
  #
435
465
  # @example
@@ -452,7 +482,6 @@ module RSpec
452
482
  @expected_received_count - 1
453
483
  end
454
484
 
455
-
456
485
  # @private
457
486
  def ordered?
458
487
  @ordered
@@ -476,15 +505,13 @@ module RSpec
476
505
  private
477
506
 
478
507
  def invoke_incrementing_actual_calls_by(increment, parent_stub, *args, &block)
479
- if yield_receiver_to_implementation_block?
480
- args.unshift(orig_object)
481
- end
508
+ args.unshift(orig_object) if yield_receiver_to_implementation_block?
482
509
 
483
510
  if negative? || ((@exactly || @at_most) && (@actual_received_count == @expected_received_count))
484
511
  @actual_received_count += increment
485
512
  @failed_fast = true
486
- #args are the args we actually received, @argument_list_matcher is the
487
- #list of args we were expecting
513
+ # args are the args we actually received, @argument_list_matcher is the
514
+ # list of args we were expecting
488
515
  @error_generator.raise_expectation_error(@message, @expected_received_count, @argument_list_matcher, @actual_received_count, expectation_count_type, *args)
489
516
  end
490
517
 
@@ -513,6 +540,7 @@ module RSpec
513
540
  when Numeric then n
514
541
  when :once then 1
515
542
  when :twice then 2
543
+ when :thrice then 3
516
544
  end
517
545
  end
518
546
 
@@ -532,7 +560,7 @@ module RSpec
532
560
 
533
561
  def warn_about_stub_override
534
562
  RSpec.warning(
535
- "You're overriding a previous stub implementation of `#{@message}`. " +
563
+ "You're overriding a previous stub implementation of `#{@message}`. " \
536
564
  "Called from #{CallerFilter.first_non_rspec_line}."
537
565
  )
538
566
  end
@@ -547,7 +575,7 @@ module RSpec
547
575
  @error_generator = error_generator
548
576
  end
549
577
 
550
- def call(*args_to_ignore, &block)
578
+ def call(*_args_to_ignore, &block)
551
579
  return if @args_to_yield.empty? && @eval_context.nil?
552
580
 
553
581
  @error_generator.raise_missing_block_error @args_to_yield unless block
@@ -555,7 +583,7 @@ module RSpec
555
583
  block_signature = Support::BlockSignature.new(block)
556
584
 
557
585
  @args_to_yield.each do |args|
558
- unless Support::MethodSignatureVerifier.new(block_signature, args).valid?
586
+ unless Support::StrictSignatureVerifier.new(block_signature, args).valid?
559
587
  @error_generator.raise_wrong_arity_error(args, block_signature)
560
588
  end
561
589
 
@@ -572,7 +600,7 @@ module RSpec
572
600
  @values_to_return = values_to_return
573
601
  end
574
602
 
575
- def call(*args_to_ignore, &block)
603
+ def call(*_args_to_ignore, &_block)
576
604
  if @values_to_return.size > 1
577
605
  @values_to_return.shift
578
606
  else
@@ -606,22 +634,23 @@ module RSpec
606
634
 
607
635
  # Represents an `and_call_original` implementation.
608
636
  # @private
609
- class AndCallOriginalImplementation
610
- def initialize(method)
637
+ class AndWrapOriginalImplementation
638
+ def initialize(method, block)
611
639
  @method = method
640
+ @block = block
612
641
  end
613
642
 
614
643
  CannotModifyFurtherError = Class.new(StandardError)
615
644
 
616
- def initial_action=(value)
645
+ def initial_action=(_value)
617
646
  raise cannot_modify_further_error
618
647
  end
619
648
 
620
- def inner_action=(value)
649
+ def inner_action=(_value)
621
650
  raise cannot_modify_further_error
622
651
  end
623
652
 
624
- def terminal_action=(value)
653
+ def terminal_action=(_value)
625
654
  raise cannot_modify_further_error
626
655
  end
627
656
 
@@ -634,13 +663,13 @@ module RSpec
634
663
  end
635
664
 
636
665
  def call(*args, &block)
637
- @method.call(*args, &block)
666
+ @block.call(@method, *args, &block)
638
667
  end
639
668
 
640
669
  private
641
670
 
642
671
  def cannot_modify_further_error
643
- CannotModifyFurtherError.new "This method has already been configured " +
672
+ CannotModifyFurtherError.new "This method has already been configured " \
644
673
  "to call the original implementation, and cannot be modified further."
645
674
  end
646
675
  end
@@ -653,9 +682,8 @@ module RSpec
653
682
  yield
654
683
  rescue RSpec::Mocks::MockExpectationError => error
655
684
  error.backtrace.insert(0, location)
656
- Kernel::raise error
685
+ Kernel.raise error
657
686
  end
658
687
  end
659
-
660
688
  end
661
689
  end