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