forked from rspec/rspec-mocks
-
Notifications
You must be signed in to change notification settings - Fork 1
/
message_expectation.rb
354 lines (298 loc) · 10.2 KB
/
message_expectation.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
module RSpec
module Mocks
class BaseExpectation
attr_reader :sym
attr_writer :expected_received_count, :method_block, :expected_from
protected :expected_received_count=, :method_block=, :expected_from=
attr_accessor :error_generator
protected :error_generator, :error_generator=
def initialize(error_generator, expectation_ordering, expected_from, sym, method_block, expected_received_count=1, opts={}, &implementation)
@error_generator = error_generator
@error_generator.opts = opts
@expected_from = expected_from
@sym = sym
@method_block = method_block
@return_block = nil
@actual_received_count = 0
@expected_received_count = expected_received_count
@args_expectation = ArgumentExpectation.new(ArgumentMatchers::AnyArgsMatcher.new)
@consecutive = false
@exception_to_raise = nil
@symbol_to_throw = nil
@order_group = expectation_ordering
@at_least = nil
@at_most = nil
@exactly = nil
@args_to_yield = []
@failed_fast = nil
@args_to_yield_were_cloned = false
@return_block = implementation
@eval_context = nil
end
def build_child(expected_from, method_block, expected_received_count, opts={})
child = clone
child.expected_from = expected_from
child.method_block = method_block
child.expected_received_count = expected_received_count
child.clear_actual_received_count!
new_gen = error_generator.clone
new_gen.opts = opts
child.error_generator = new_gen
child.clone_args_to_yield(*@args_to_yield)
child
end
def expected_args
@args_expectation.args
end
def and_return(*values, &return_block)
Kernel::raise AmbiguousReturnError unless @method_block.nil?
case values.size
when 0 then value = nil
when 1 then value = values[0]
else
value = values
@consecutive = true
@expected_received_count = values.size if !ignoring_args? &&
@expected_received_count < values.size
end
@return_block = block_given? ? return_block : lambda { value }
end
# :call-seq:
# and_raise()
# and_raise(Exception) #any exception class
# and_raise(exception) #any exception object
#
# == Warning
#
# When you pass an exception class, the MessageExpectation will
# raise an instance of it, creating it with +new+. If the exception
# class initializer requires any parameters, you must pass in an
# instance and not the class.
def and_raise(exception=Exception)
@exception_to_raise = exception
end
def and_throw(symbol)
@symbol_to_throw = symbol
end
def and_yield(*args, &block)
if @args_to_yield_were_cloned
@args_to_yield.clear
@args_to_yield_were_cloned = false
end
if block
@eval_context = Object.new
@eval_context.extend RSpec::Mocks::InstanceExec
yield @eval_context
end
@args_to_yield << args
self
end
def matches?(sym, *args)
@sym == sym and @args_expectation.args_match?(*args)
end
def invoke(*args, &block)
if @expected_received_count == 0
@failed_fast = true
@actual_received_count += 1
@error_generator.raise_expectation_error(@sym, @expected_received_count, @actual_received_count, *args)
end
@order_group.handle_order_constraint self
begin
Kernel::raise @exception_to_raise unless @exception_to_raise.nil?
Kernel::throw @symbol_to_throw unless @symbol_to_throw.nil?
default_return_val = if !@method_block.nil?
invoke_method_block(*args, &block)
elsif !@args_to_yield.empty? || @eval_context
invoke_with_yield(&block)
else
nil
end
if @consecutive
invoke_consecutive_return_block(*args, &block)
elsif @return_block
invoke_return_block(*args, &block)
else
default_return_val
end
ensure
@actual_received_count += 1
end
end
def called_max_times?
@expected_received_count != :any && @expected_received_count > 0 &&
@actual_received_count >= @expected_received_count
end
protected
def invoke_method_block(*args, &block)
begin
@method_block.call(*args, &block)
rescue => detail
@error_generator.raise_block_failed_error(@sym, detail.message)
end
end
def invoke_with_yield(&block)
if block.nil?
@error_generator.raise_missing_block_error @args_to_yield
end
value = nil
@args_to_yield.each do |args_to_yield_this_time|
if block.arity > -1 && args_to_yield_this_time.length != block.arity
@error_generator.raise_wrong_arity_error args_to_yield_this_time, block.arity
end
value = eval_block(*args_to_yield_this_time, &block)
end
value
end
def eval_block(*args, &block)
if @eval_context
@eval_context.instance_exec(*args, &block)
else
block.call(*args)
end
end
def invoke_consecutive_return_block(*args, &block)
value = invoke_return_block(*args, &block)
index = [@actual_received_count, value.size-1].min
value[index]
end
def invoke_return_block(*args, &block)
args << block unless block.nil?
# Ruby 1.9 - when we set @return_block to return values
# regardless of arguments, any arguments will result in
# a "wrong number of arguments" error
@return_block.arity == 0 ? @return_block.call : @return_block.call(*args)
end
def clone_args_to_yield(*args)
@args_to_yield = args.clone
@args_to_yield_were_cloned = true
end
def failed_fast?
@failed_fast
end
end
class MessageExpectation < BaseExpectation
def matches_name_but_not_args(sym, *args)
@sym == sym and not @args_expectation.args_match?(*args)
end
def verify_messages_received
return if expected_messages_received? || failed_fast?
generate_error
rescue RSpec::Mocks::MockExpectationError => error
error.backtrace.insert(0, @expected_from)
Kernel::raise error
end
def expected_messages_received?
ignoring_args? || matches_exact_count? ||
matches_at_least_count? || matches_at_most_count?
end
def ignoring_args?
@expected_received_count == :any
end
def matches_at_least_count?
@at_least && @actual_received_count >= @expected_received_count
end
def matches_at_most_count?
@at_most && @actual_received_count <= @expected_received_count
end
def matches_exact_count?
@expected_received_count == @actual_received_count
end
def similar_messages
@similar_messages ||= []
end
def advise(*args)
similar_messages << args
end
def generate_error
if similar_messages.empty?
@error_generator.raise_expectation_error(@sym, @expected_received_count, @actual_received_count, *@args_expectation.args)
else
@error_generator.raise_similar_message_args_error(self, *@similar_messages)
end
end
def with(*args, &block)
@return_block = block if block_given?
@args_expectation = ArgumentExpectation.new(*args, &block)
self
end
def exactly(n, &block)
@method_block = block if block
set_expected_received_count :exactly, n
self
end
def at_least(n, &block)
@method_block = block if block
set_expected_received_count :at_least, n
self
end
def at_most(n, &block)
@method_block = block if block
set_expected_received_count :at_most, n
self
end
def times(&block)
@method_block = block if block
self
end
def any_number_of_times(&block)
@method_block = block if block
@expected_received_count = :any
self
end
def never
@expected_received_count = 0
self
end
def once(&block)
@method_block = block if block
set_expected_received_count :exactly, 1
self
end
def twice(&block)
@method_block = block if block
set_expected_received_count :exactly, 2
self
end
def ordered(&block)
@method_block = block if block
@order_group.register(self)
@ordered = true
self
end
def negative_expectation_for?(sym)
return false
end
def actual_received_count_matters?
@at_least || @at_most || @exactly
end
def increase_actual_received_count!
@actual_received_count += 1
end
protected
def set_expected_received_count(relativity, n)
@at_least = (relativity == :at_least)
@at_most = (relativity == :at_most)
@exactly = (relativity == :exactly)
@expected_received_count = case n
when Numeric
n
when :once
1
when :twice
2
end
end
def clear_actual_received_count!
@actual_received_count = 0
end
end
class NegativeMessageExpectation < MessageExpectation
def initialize(message, expectation_ordering, expected_from, sym, method_block)
super(message, expectation_ordering, expected_from, sym, method_block, 0)
end
def negative_expectation_for?(sym)
return @sym == sym
end
end
end
end