flexmock 0.5.1 → 0.6.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.
- data/CHANGELOG +10 -1
- data/README +390 -209
- data/Rakefile +31 -10
- data/doc/GoogleExample.rdoc +275 -0
- data/doc/releases/flexmock-0.6.0.rdoc +136 -0
- data/lib/flexmock.rb +3 -1160
- data/lib/flexmock/argument_matchers.rb +57 -0
- data/lib/flexmock/argument_types.rb +42 -0
- data/lib/flexmock/base.rb +22 -0
- data/lib/flexmock/composite.rb +10 -0
- data/lib/flexmock/core.rb +206 -0
- data/lib/flexmock/core_class_methods.rb +92 -0
- data/lib/flexmock/default_framework_adapter.rb +31 -0
- data/lib/flexmock/expectation.rb +334 -0
- data/lib/flexmock/expectation_director.rb +59 -0
- data/lib/flexmock/mock_container.rb +159 -0
- data/lib/flexmock/noop.rb +13 -0
- data/lib/flexmock/partial_mock.rb +226 -0
- data/lib/flexmock/recorder.rb +71 -0
- data/lib/flexmock/rspec.rb +34 -0
- data/lib/flexmock/test_unit.rb +32 -0
- data/lib/flexmock/test_unit_integration.rb +53 -0
- data/lib/flexmock/validators.rb +77 -0
- data/test/rspec_integration/integration_spec.rb +36 -0
- data/test/test_container_methods.rb +119 -0
- data/test/test_default_framework_adapter.rb +39 -0
- data/test/test_example.rb +1 -1
- data/test/test_extended_should_receive.rb +63 -0
- data/test/test_mock.rb +1 -1
- data/test/test_naming.rb +1 -1
- data/test/{test_any_instance.rb → test_new_instances.rb} +15 -8
- data/test/{test_stubbing.rb → test_partial_mock.rb} +44 -44
- data/test/test_record_mode.rb +1 -1
- data/test/test_samples.rb +6 -8
- data/test/test_should_receive.rb +7 -3
- data/test/test_tu_integration.rb +1 -1
- data/test/test_unit_integration/test_auto_test_unit.rb +34 -0
- metadata +30 -5
- data/test/test_class_interception.rb +0 -140
data/lib/flexmock.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
#---
|
4
|
-
# Copyright 2003, 2004, 2005, 2006 by Jim Weirich (jim@weirichhouse.org).
|
4
|
+
# Copyright 2003, 2004, 2005, 2006, 2007 by Jim Weirich (jim@weirichhouse.org).
|
5
5
|
# All rights reserved.
|
6
6
|
|
7
7
|
# Permission is granted for use, copying, modification, distribution,
|
@@ -9,1162 +9,5 @@
|
|
9
9
|
# above copyright notice is included.
|
10
10
|
#+++
|
11
11
|
|
12
|
-
require '
|
13
|
-
|
14
|
-
######################################################################
|
15
|
-
# FlexMock is a flexible mock object suitable for using with Ruby's
|
16
|
-
# Test::Unit unit test framework. FlexMock has a simple interface
|
17
|
-
# that's easy to remember, and leaves the hard stuff to all those
|
18
|
-
# other mock object implementations.
|
19
|
-
#
|
20
|
-
# Basic Usage:
|
21
|
-
#
|
22
|
-
# m = FlexMock.new("name")
|
23
|
-
# m.mock_handle(:meth) { |args| assert_stuff }
|
24
|
-
#
|
25
|
-
# Simplified Usage:
|
26
|
-
#
|
27
|
-
# m = FlexMock.new("name")
|
28
|
-
# m.should_receive(:upcase).with("stuff").
|
29
|
-
# and_return("STUFF")
|
30
|
-
# m.should_receive(:downcase).with(String).
|
31
|
-
# and_return { |s| s.downcase }.once
|
32
|
-
#
|
33
|
-
# With Test::Unit Integration:
|
34
|
-
#
|
35
|
-
# class TestSomething < Test::Unit::TestCase
|
36
|
-
# include FlexMock::TestCase
|
37
|
-
#
|
38
|
-
# def test_something
|
39
|
-
# m = flexmock("name")
|
40
|
-
# m.should_receive(:hi).and_return("Hello")
|
41
|
-
# m.hi
|
42
|
-
# end
|
43
|
-
# end
|
44
|
-
#
|
45
|
-
# Note: When using Test::Unit integeration, don't forget to include
|
46
|
-
# FlexMock::TestCase. Also, if you override +teardown+, make sure you
|
47
|
-
# call +super+.
|
48
|
-
#
|
49
|
-
class FlexMock
|
50
|
-
include Test::Unit::Assertions
|
51
|
-
|
52
|
-
class BadInterceptionError < RuntimeError; end
|
53
|
-
|
54
|
-
attr_reader :mock_name, :mock_groups
|
55
|
-
attr_accessor :mock_current_order, :mock_container
|
56
|
-
|
57
|
-
# Create a FlexMock object with the given name. The name is used in
|
58
|
-
# error messages.
|
59
|
-
def initialize(name="unknown")
|
60
|
-
@mock_name = name
|
61
|
-
@expectations = Hash.new
|
62
|
-
@allocated_order = 0
|
63
|
-
@mock_current_order = 0
|
64
|
-
@mock_container = nil
|
65
|
-
@mock_groups = {}
|
66
|
-
@ignore_missing = false
|
67
|
-
@verified = false
|
68
|
-
end
|
69
|
-
|
70
|
-
# Handle all messages denoted by +sym+ by calling the given block
|
71
|
-
# and passing any parameters to the block. If we know exactly how
|
72
|
-
# many calls are to be made to a particular method, we may check
|
73
|
-
# that by passing in the number of expected calls as a second
|
74
|
-
# paramter.
|
75
|
-
def mock_handle(sym, expected_count=nil, &block)
|
76
|
-
self.should_receive(sym).times(expected_count).returns(&block)
|
77
|
-
end
|
78
|
-
|
79
|
-
# Verify that each method that had an explicit expected count was
|
80
|
-
# actually called that many times.
|
81
|
-
def mock_verify
|
82
|
-
return if @verified
|
83
|
-
@verified = true
|
84
|
-
mock_wrap do
|
85
|
-
@expectations.each do |sym, handler|
|
86
|
-
handler.mock_verify
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
# Teardown and infrastructure setup for this mock.
|
92
|
-
def mock_teardown
|
93
|
-
end
|
94
|
-
|
95
|
-
# Allocation a new order number from the mock.
|
96
|
-
def mock_allocate_order
|
97
|
-
@auto_allocate = true
|
98
|
-
@allocated_order += 1
|
99
|
-
end
|
100
|
-
|
101
|
-
# Ignore all undefined (missing) method calls.
|
102
|
-
def should_ignore_missing
|
103
|
-
@ignore_missing = true
|
104
|
-
end
|
105
|
-
alias mock_ignore_missing should_ignore_missing
|
106
|
-
|
107
|
-
# Handle missing methods by attempting to look up a handler.
|
108
|
-
def method_missing(sym, *args, &block)
|
109
|
-
mock_wrap do
|
110
|
-
if handler = @expectations[sym]
|
111
|
-
args << block if block_given?
|
112
|
-
handler.call(*args)
|
113
|
-
else
|
114
|
-
super(sym, *args, &block) unless @ignore_missing
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
# Save the original definition of respond_to? for use a bit later.
|
120
|
-
alias mock_respond_to? respond_to?
|
121
|
-
|
122
|
-
# Override the built-in respond_to? to include the mocked methods.
|
123
|
-
def respond_to?(sym)
|
124
|
-
super || (@expectations[sym] ? true : @ignore_missing)
|
125
|
-
end
|
126
|
-
|
127
|
-
# Override the built-in +method+ to include the mocked methods.
|
128
|
-
def method(sym)
|
129
|
-
@expectations[sym] || super
|
130
|
-
rescue NameError => ex
|
131
|
-
if @ignore_missing
|
132
|
-
proc { }
|
133
|
-
else
|
134
|
-
raise ex
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
# Declare that the mock object should receive a message with the
|
139
|
-
# given name. An expectation object for the method name is returned
|
140
|
-
# as the result of this method. Further expectation constraints can
|
141
|
-
# be added by chaining to the result.
|
142
|
-
#
|
143
|
-
# See Expectation for a list of declarators that can be used.
|
144
|
-
def should_receive(sym)
|
145
|
-
@expectations[sym] ||= ExpectationDirector.new(sym)
|
146
|
-
result = Expectation.new(self, sym)
|
147
|
-
@expectations[sym] << result
|
148
|
-
override_existing_method(sym) if mock_respond_to?(sym)
|
149
|
-
result
|
150
|
-
end
|
151
|
-
|
152
|
-
# Override the existing definition of method +sym+ in the mock.
|
153
|
-
# Most methods depend on the method_missing trick to be invoked.
|
154
|
-
# However, if the method already exists, it will not call
|
155
|
-
# method_missing. This method defines a singleton method on the
|
156
|
-
# mock to explicitly invoke the method_missing logic.
|
157
|
-
def override_existing_method(sym)
|
158
|
-
sclass.class_eval <<-EOS
|
159
|
-
def #{sym}(*args, &block)
|
160
|
-
method_missing(:#{sym}, *args, &block)
|
161
|
-
end
|
162
|
-
EOS
|
163
|
-
end
|
164
|
-
private :override_existing_method
|
165
|
-
|
166
|
-
# Return the singleton class of the mock object.
|
167
|
-
def sclass
|
168
|
-
class << self; self; end
|
169
|
-
end
|
170
|
-
private :sclass
|
171
|
-
|
172
|
-
# Declare that the mock object should expect methods by providing a
|
173
|
-
# recorder for the methods and having the user invoke the expected
|
174
|
-
# methods in a block. Further expectations may be applied the
|
175
|
-
# result of the recording call.
|
176
|
-
#
|
177
|
-
# Example Usage:
|
178
|
-
#
|
179
|
-
# mock.should_expect do |record|
|
180
|
-
# record.add(Integer, 4) { |a, b|
|
181
|
-
# a + b
|
182
|
-
# }.at_least.once
|
183
|
-
#
|
184
|
-
def should_expect
|
185
|
-
yield Recorder.new(self)
|
186
|
-
end
|
187
|
-
|
188
|
-
# Return a factory object that returns this mock. This is useful in
|
189
|
-
# Class Interception.
|
190
|
-
def mock_factory
|
191
|
-
Factory.new(self)
|
192
|
-
end
|
193
|
-
|
194
|
-
class << self
|
195
|
-
include Test::Unit::Assertions
|
196
|
-
|
197
|
-
# Class method to make sure that verify is called at the end of a
|
198
|
-
# test. One mock object will be created for each name given to
|
199
|
-
# the use method. The mocks will be passed to the block as
|
200
|
-
# arguments. If no names are given, then a single anonymous mock
|
201
|
-
# object will be created.
|
202
|
-
#
|
203
|
-
# At the end of the use block, each mock object will be verified
|
204
|
-
# to make sure the proper number of calls have been made.
|
205
|
-
#
|
206
|
-
# Usage:
|
207
|
-
#
|
208
|
-
# FlexMock.use("name") do |mock| # Creates a mock named "name"
|
209
|
-
# mock.should_receive(:meth).
|
210
|
-
# returns(0).once
|
211
|
-
# end # mock is verified here
|
212
|
-
#
|
213
|
-
# NOTE: If you include FlexMock::TestCase into your test case
|
214
|
-
# file, you can create mocks that will be automatically verified in
|
215
|
-
# the test teardown by using the +flexmock+ method.
|
216
|
-
#
|
217
|
-
def use(*names)
|
218
|
-
names = ["unknown"] if names.empty?
|
219
|
-
got_excecption = false
|
220
|
-
mocks = names.collect { |n| new(n) }
|
221
|
-
yield(*mocks)
|
222
|
-
rescue Exception => ex
|
223
|
-
got_exception = true
|
224
|
-
raise
|
225
|
-
ensure
|
226
|
-
mocks.each do |mock|
|
227
|
-
mock.mock_verify unless got_exception
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
# Class method to format a method name and argument list as a nice
|
232
|
-
# looking string.
|
233
|
-
def format_args(sym, args)
|
234
|
-
if args
|
235
|
-
"#{sym}(#{args.collect { |a| a.inspect }.join(', ')})"
|
236
|
-
else
|
237
|
-
"#{sym}(*args)"
|
238
|
-
end
|
239
|
-
end
|
240
|
-
|
241
|
-
# Check will assert the block returns true. If it doesn't, an
|
242
|
-
# assertion failure is triggered with the given message.
|
243
|
-
def check(msg, &block)
|
244
|
-
assert_block(msg, &block)
|
245
|
-
end
|
246
|
-
end
|
247
|
-
|
248
|
-
private
|
249
|
-
|
250
|
-
# Wrap a block of code so the any assertion errors are wrapped so
|
251
|
-
# that the mock name is added to the error message .
|
252
|
-
def mock_wrap(&block)
|
253
|
-
yield
|
254
|
-
rescue Test::Unit::AssertionFailedError => ex
|
255
|
-
raise Test::Unit::AssertionFailedError,
|
256
|
-
"in mock '#{@mock_name}': #{ex.message}",
|
257
|
-
ex.backtrace
|
258
|
-
end
|
259
|
-
|
260
|
-
####################################################################
|
261
|
-
# A Factory object is returned from a mock_factory method call. The
|
262
|
-
# factory merely returns the manufactured object it is initialized
|
263
|
-
# with. The factory is handy to use with class interception,
|
264
|
-
# allowing the intercepted class to return the mock object.
|
265
|
-
#
|
266
|
-
# If the user needs more control over the mock factory, they are
|
267
|
-
# free to create their own.
|
268
|
-
#
|
269
|
-
# Typical Usage:
|
270
|
-
# intercept(Bar).in(Foo).with(a_mock.mack_factory)
|
271
|
-
#
|
272
|
-
class Factory
|
273
|
-
def initialize(manufactured_object)
|
274
|
-
@obj = manufactured_object
|
275
|
-
end
|
276
|
-
def new(*args, &block)
|
277
|
-
@obj
|
278
|
-
end
|
279
|
-
end
|
280
|
-
|
281
|
-
####################################################################
|
282
|
-
# The expectation director is responsible for routing calls to the
|
283
|
-
# correct expectations for a given argument list.
|
284
|
-
#
|
285
|
-
class ExpectationDirector
|
286
|
-
|
287
|
-
# Create an ExpectationDirector for a mock object.
|
288
|
-
def initialize(sym)
|
289
|
-
@sym = sym
|
290
|
-
@expectations = []
|
291
|
-
@expected_order = nil
|
292
|
-
end
|
293
|
-
|
294
|
-
# Invoke the expectations for a given set of arguments.
|
295
|
-
#
|
296
|
-
# First, look for an expectation that matches the arguements and
|
297
|
-
# is eligible to be called. Failing that, look for a expectation
|
298
|
-
# that matches the arguments (at this point it will be ineligible,
|
299
|
-
# but at least we will get a good failure message). Finally,
|
300
|
-
# check for expectations that don't have any argument matching
|
301
|
-
# criteria.
|
302
|
-
def call(*args)
|
303
|
-
exp = @expectations.find { |e| e.match_args(args) && e.eligible? } ||
|
304
|
-
@expectations.find { |e| e.match_args(args) }
|
305
|
-
FlexMock.check("no matching handler found for " +
|
306
|
-
FlexMock.format_args(@sym, args)) { ! exp.nil? }
|
307
|
-
exp.verify_call(*args)
|
308
|
-
end
|
309
|
-
|
310
|
-
# Append an expectation to this director.
|
311
|
-
def <<(expectation)
|
312
|
-
@expectations << expectation
|
313
|
-
end
|
314
|
-
|
315
|
-
# Do the post test verification for this directory. Check all the
|
316
|
-
# expectations.
|
317
|
-
def mock_verify
|
318
|
-
@expectations.each do |exp|
|
319
|
-
exp.mock_verify
|
320
|
-
end
|
321
|
-
end
|
322
|
-
end
|
323
|
-
|
324
|
-
####################################################################
|
325
|
-
# Match any object
|
326
|
-
class AnyMatcher
|
327
|
-
def ===(target)
|
328
|
-
true
|
329
|
-
end
|
330
|
-
def inspect
|
331
|
-
"ANY"
|
332
|
-
end
|
333
|
-
end
|
334
|
-
|
335
|
-
####################################################################
|
336
|
-
# Match only things that are equal.
|
337
|
-
class EqualMatcher
|
338
|
-
def initialize(obj)
|
339
|
-
@obj = obj
|
340
|
-
end
|
341
|
-
def ===(target)
|
342
|
-
@obj == target
|
343
|
-
end
|
344
|
-
def inspect
|
345
|
-
"==(#{@obj.inspect})"
|
346
|
-
end
|
347
|
-
end
|
348
|
-
|
349
|
-
ANY = AnyMatcher.new
|
350
|
-
|
351
|
-
####################################################################
|
352
|
-
# Match only things where the block evaluates to true.
|
353
|
-
class ProcMatcher
|
354
|
-
def initialize(&block)
|
355
|
-
@block = block
|
356
|
-
end
|
357
|
-
def ===(target)
|
358
|
-
@block.call(target)
|
359
|
-
end
|
360
|
-
def inspect
|
361
|
-
"on{...}"
|
362
|
-
end
|
363
|
-
end
|
364
|
-
|
365
|
-
####################################################################
|
366
|
-
# Include this module in your test class if you wish to use the +eq+
|
367
|
-
# and +any+ argument matching methods without a prefix. (Otherwise
|
368
|
-
# use <tt>FlexMock.any</tt> and <tt>FlexMock.eq(obj)</tt>.
|
369
|
-
#
|
370
|
-
module ArgumentTypes
|
371
|
-
# Return an argument matcher that matches any argument.
|
372
|
-
def any
|
373
|
-
ANY
|
374
|
-
end
|
375
|
-
|
376
|
-
# Return an argument matcher that only matches things equal to
|
377
|
-
# (==) the given object.
|
378
|
-
def eq(obj)
|
379
|
-
EqualMatcher.new(obj)
|
380
|
-
end
|
381
|
-
|
382
|
-
# Return an argument matcher that matches any object, that when
|
383
|
-
# passed to the supplied block, will cause the block to return
|
384
|
-
# true.
|
385
|
-
def on(&block)
|
386
|
-
ProcMatcher.new(&block)
|
387
|
-
end
|
388
|
-
end
|
389
|
-
extend ArgumentTypes
|
390
|
-
|
391
|
-
####################################################################
|
392
|
-
# Base class for all the count validators.
|
393
|
-
#
|
394
|
-
class CountValidator
|
395
|
-
include Test::Unit::Assertions
|
396
|
-
def initialize(expectation, limit)
|
397
|
-
@exp = expectation
|
398
|
-
@limit = limit
|
399
|
-
end
|
400
|
-
|
401
|
-
# If the expectation has been called +n+ times, is it still
|
402
|
-
# eligible to be called again? The default answer compares n to
|
403
|
-
# the established limit.
|
404
|
-
def eligible?(n)
|
405
|
-
n < @limit
|
406
|
-
end
|
407
|
-
end
|
408
|
-
|
409
|
-
####################################################################
|
410
|
-
# Validator for exact call counts.
|
411
|
-
#
|
412
|
-
class ExactCountValidator < CountValidator
|
413
|
-
# Validate that the method expectation was called exactly +n+
|
414
|
-
# times.
|
415
|
-
def validate(n)
|
416
|
-
assert_equal @limit, n,
|
417
|
-
"method '#{@exp}' called incorrect number of times"
|
418
|
-
end
|
419
|
-
end
|
420
|
-
|
421
|
-
####################################################################
|
422
|
-
# Validator for call counts greater than or equal to a limit.
|
423
|
-
#
|
424
|
-
class AtLeastCountValidator < CountValidator
|
425
|
-
# Validate the method expectation was called no more than +n+
|
426
|
-
# times.
|
427
|
-
def validate(n)
|
428
|
-
assert n >= @limit,
|
429
|
-
"Method '#{@exp}' should be called at least #{@limit} times,\n" +
|
430
|
-
"only called #{n} times"
|
431
|
-
end
|
432
|
-
|
433
|
-
# If the expectation has been called +n+ times, is it still
|
434
|
-
# eligible to be called again? Since this validator only
|
435
|
-
# establishes a lower limit, not an upper limit, then the answer
|
436
|
-
# is always true.
|
437
|
-
def eligible?(n)
|
438
|
-
true
|
439
|
-
end
|
440
|
-
end
|
441
|
-
|
442
|
-
####################################################################
|
443
|
-
# Validator for call counts less than or equal to a limit.
|
444
|
-
#
|
445
|
-
class AtMostCountValidator < CountValidator
|
446
|
-
# Validate the method expectation was called at least +n+ times.
|
447
|
-
def validate(n)
|
448
|
-
assert n <= @limit,
|
449
|
-
"Method '#{@exp}' should be called at most #{@limit} times,\n" +
|
450
|
-
"only called #{n} times"
|
451
|
-
end
|
452
|
-
end
|
453
|
-
|
454
|
-
####################################################################
|
455
|
-
# An Expectation is returned from each +should_receive+ message sent
|
456
|
-
# to mock object. Each expectation records how a message matching
|
457
|
-
# the message name (argument to +should_receive+) and the argument
|
458
|
-
# list (given by +with+) should behave. Mock expectations can be
|
459
|
-
# recorded by chaining the declaration methods defined in this
|
460
|
-
# class.
|
461
|
-
#
|
462
|
-
# For example:
|
463
|
-
#
|
464
|
-
# mock.should_receive(:meth).with(args).and_returns(result)
|
465
|
-
#
|
466
|
-
class Expectation
|
467
|
-
include Test::Unit::Assertions
|
468
|
-
|
469
|
-
attr_reader :expected_args, :mock, :order_number
|
470
|
-
|
471
|
-
# Create an expectation for a method named +sym+.
|
472
|
-
def initialize(mock, sym)
|
473
|
-
@mock = mock
|
474
|
-
@sym = sym
|
475
|
-
@expected_args = nil
|
476
|
-
@count_validators = []
|
477
|
-
@count_validator_class = ExactCountValidator
|
478
|
-
@actual_count = 0
|
479
|
-
@return_value = nil
|
480
|
-
@return_block = lambda { @return_value }
|
481
|
-
@order_number = nil
|
482
|
-
end
|
483
|
-
|
484
|
-
def to_s
|
485
|
-
FlexMock.format_args(@sym, @expected_args)
|
486
|
-
end
|
487
|
-
|
488
|
-
# Verify the current call with the given arguments matches the
|
489
|
-
# expectations recorded in this object.
|
490
|
-
def verify_call(*args)
|
491
|
-
validate_order
|
492
|
-
@actual_count += 1
|
493
|
-
@return_block.call(*args)
|
494
|
-
end
|
495
|
-
|
496
|
-
# Is this expectation eligible to be called again? It is eligible
|
497
|
-
# only if all of its count validators agree that it is eligible.
|
498
|
-
def eligible?
|
499
|
-
@count_validators.all? { |v| v.eligible?(@actual_count) }
|
500
|
-
end
|
501
|
-
|
502
|
-
# Validate that the order
|
503
|
-
def validate_order
|
504
|
-
return if @order_number.nil?
|
505
|
-
FlexMock.check("method #{to_s} called out of order " +
|
506
|
-
"(expected order #{@order_number}, was #{@mock.mock_current_order})") {
|
507
|
-
@order_number >= @mock.mock_current_order
|
508
|
-
}
|
509
|
-
@mock.mock_current_order = @order_number
|
510
|
-
end
|
511
|
-
private :validate_order
|
512
|
-
|
513
|
-
# Validate the correct number of calls have been made. Called by
|
514
|
-
# the teardown process.
|
515
|
-
def mock_verify
|
516
|
-
@count_validators.each do |v|
|
517
|
-
v.validate(@actual_count)
|
518
|
-
end
|
519
|
-
end
|
520
|
-
|
521
|
-
# Does the argument list match this expectation's argument
|
522
|
-
# specification.
|
523
|
-
def match_args(args)
|
524
|
-
# TODO: Rethink this:
|
525
|
-
# return false if @expected_args.nil?
|
526
|
-
return true if @expected_args.nil?
|
527
|
-
return false if args.size != @expected_args.size
|
528
|
-
(0...args.size).all? { |i| match_arg(@expected_args[i], args[i]) }
|
529
|
-
end
|
530
|
-
|
531
|
-
# Does the expected argument match the corresponding actual value.
|
532
|
-
def match_arg(expected, actual)
|
533
|
-
expected === actual ||
|
534
|
-
expected == actual ||
|
535
|
-
( Regexp === expected && expected === actual.to_s )
|
536
|
-
end
|
537
|
-
|
538
|
-
# Declare that the method should expect the given argument list.
|
539
|
-
def with(*args)
|
540
|
-
@expected_args = args
|
541
|
-
self
|
542
|
-
end
|
543
|
-
|
544
|
-
# Declare that the method should be called with no arguments.
|
545
|
-
def with_no_args
|
546
|
-
with
|
547
|
-
end
|
548
|
-
|
549
|
-
# Declare that the method can be called with any number of
|
550
|
-
# arguments of any type.
|
551
|
-
def with_any_args
|
552
|
-
@expected_args = nil
|
553
|
-
self
|
554
|
-
end
|
555
|
-
|
556
|
-
# Declare that the method returns a particular value (when the
|
557
|
-
# argument list is matched).
|
558
|
-
#
|
559
|
-
# * If a single value is given, it will be returned for all matching
|
560
|
-
# calls.
|
561
|
-
# * If multiple values are given, each value will be returned in turn for
|
562
|
-
# each successive call. If the number of matching calls is greater
|
563
|
-
# than the number of values, the last value will be returned for
|
564
|
-
# the extra matching calls.
|
565
|
-
# * If a block is given, it is evaluated on each call and its
|
566
|
-
# value is returned.
|
567
|
-
#
|
568
|
-
# For example:
|
569
|
-
#
|
570
|
-
# mock.should_receive(:f).returns(12) # returns 12
|
571
|
-
#
|
572
|
-
# mock.should_receive(:f).with(String). # returns an
|
573
|
-
# returns { |str| str.upcase } # upcased string
|
574
|
-
#
|
575
|
-
# +and_return+ is an alias for +returns+.
|
576
|
-
#
|
577
|
-
def returns(*args, &block)
|
578
|
-
@return_block =
|
579
|
-
if block_given?
|
580
|
-
block
|
581
|
-
else
|
582
|
-
lambda { args.size == 1 ? args.first : args.shift }
|
583
|
-
end
|
584
|
-
self
|
585
|
-
end
|
586
|
-
alias :and_return :returns # :nodoc:
|
587
|
-
|
588
|
-
# Declare that the method may be called any number of times.
|
589
|
-
def zero_or_more_times
|
590
|
-
at_least.never
|
591
|
-
end
|
592
|
-
|
593
|
-
# Declare that the method is called +limit+ times with the
|
594
|
-
# declared argument list. This may be modified by the +at_least+
|
595
|
-
# and +at_most+ declarators.
|
596
|
-
def times(limit)
|
597
|
-
@count_validators << @count_validator_class.new(self, limit) unless limit.nil?
|
598
|
-
@count_validator_class = ExactCountValidator
|
599
|
-
self
|
600
|
-
end
|
601
|
-
|
602
|
-
# Declare that the method is never expected to be called with the
|
603
|
-
# given argument list. This may be modified by the +at_least+ and
|
604
|
-
# +at_most+ declarators.
|
605
|
-
def never
|
606
|
-
times(0)
|
607
|
-
end
|
608
|
-
|
609
|
-
# Declare that the method is expected to be called exactly once
|
610
|
-
# with the given argument list. This may be modified by the
|
611
|
-
# +at_least+ and +at_most+ declarators.
|
612
|
-
def once
|
613
|
-
times(1)
|
614
|
-
end
|
615
|
-
|
616
|
-
# Declare that the method is expected to be called exactly twice
|
617
|
-
# with the given argument list. This may be modified by the
|
618
|
-
# +at_least+ and +at_most+ declarators.
|
619
|
-
def twice
|
620
|
-
times(2)
|
621
|
-
end
|
622
|
-
|
623
|
-
# Modifies the next call count declarator (+times+, +never+,
|
624
|
-
# +once+ or +twice+) so that the declarator means the method is
|
625
|
-
# called at least that many times.
|
626
|
-
#
|
627
|
-
# E.g. method f must be called at least twice:
|
628
|
-
#
|
629
|
-
# mock.should_receive(:f).at_least.twice
|
630
|
-
#
|
631
|
-
def at_least
|
632
|
-
@count_validator_class = AtLeastCountValidator
|
633
|
-
self
|
634
|
-
end
|
635
|
-
|
636
|
-
# Modifies the next call count declarator (+times+, +never+,
|
637
|
-
# +once+ or +twice+) so that the declarator means the method is
|
638
|
-
# called at most that many times.
|
639
|
-
#
|
640
|
-
# E.g. method f must be called no more than twice
|
641
|
-
#
|
642
|
-
# mock.should_receive(:f).at_most.twice
|
643
|
-
#
|
644
|
-
def at_most
|
645
|
-
@count_validator_class = AtMostCountValidator
|
646
|
-
self
|
647
|
-
end
|
648
|
-
|
649
|
-
# Declare that the given method must be called in order. All
|
650
|
-
# ordered method calls must be received in the order specified by
|
651
|
-
# the ordering of the +should_receive+ messages. Receiving a
|
652
|
-
# methods out of the specified order will cause a test failure.
|
653
|
-
#
|
654
|
-
# If the user needs more fine control over ordering
|
655
|
-
# (e.g. specifying that a group of messages may be received in any
|
656
|
-
# order as long as they all come after another group of messages),
|
657
|
-
# a _group_ _name_ may be specified in the +ordered+ calls. All
|
658
|
-
# messages within the same group may be received in any order.
|
659
|
-
#
|
660
|
-
# For example, in the following, messages +flip+ and +flop+ may be
|
661
|
-
# received in any order (because they are in the same group), but
|
662
|
-
# must occur strictly after +start+ but before +end+. The message
|
663
|
-
# +any_time+ may be received at any time because it is not
|
664
|
-
# ordered.
|
665
|
-
#
|
666
|
-
# m = FlexMock.new
|
667
|
-
# m.should_receive(:any_time)
|
668
|
-
# m.should_receive(:start).ordered
|
669
|
-
# m.should_receive(:flip).ordered(:flip_flop_group)
|
670
|
-
# m.should_receive(:flop).ordered(:flip_flop_group)
|
671
|
-
# m.should_receive(:end).ordered
|
672
|
-
#
|
673
|
-
def ordered(group_name=nil)
|
674
|
-
if group_name.nil?
|
675
|
-
@order_number = @mock.mock_allocate_order
|
676
|
-
elsif (num = @mock.mock_groups[group_name])
|
677
|
-
@order_number = num
|
678
|
-
else
|
679
|
-
@order_number = @mock.mock_allocate_order
|
680
|
-
@mock.mock_groups[group_name] = @order_number
|
681
|
-
end
|
682
|
-
self
|
683
|
-
end
|
684
|
-
end
|
685
|
-
|
686
|
-
####################################################################
|
687
|
-
# Translate arbitrary method calls into expectations on the given
|
688
|
-
# mock object.
|
689
|
-
#
|
690
|
-
class Recorder
|
691
|
-
include FlexMock::ArgumentTypes
|
692
|
-
|
693
|
-
# Create a method recorder for the mock +mock+.
|
694
|
-
def initialize(mock)
|
695
|
-
@mock = mock
|
696
|
-
@strict = false
|
697
|
-
end
|
698
|
-
|
699
|
-
# Place the record in strict mode. While recording expectations
|
700
|
-
# in strict mode, the following will be true.
|
701
|
-
#
|
702
|
-
# * All expectations will be expected in the order they were
|
703
|
-
# recorded.
|
704
|
-
# * All expectations will be expected once.
|
705
|
-
# * All arguments will be placed in exact match mode,
|
706
|
-
# including regular expressions and class objects.
|
707
|
-
#
|
708
|
-
# Strict mode is usually used when giving the recorder to a known
|
709
|
-
# good algorithm. Strict mode captures the exact sequence of
|
710
|
-
# calls and validate that the code under test performs the exact
|
711
|
-
# same sequence of calls.
|
712
|
-
#
|
713
|
-
# The recorder may exit strict mode via a
|
714
|
-
# <tt>should_be_strict(false)</tt> call. Non-strict expectations
|
715
|
-
# may be recorded at that point, or even explicit expectations
|
716
|
-
# (using +should_receieve+) can be specified.
|
717
|
-
#
|
718
|
-
def should_be_strict(is_strict=true)
|
719
|
-
@strict = is_strict
|
720
|
-
end
|
721
|
-
|
722
|
-
# Is the recorder in strict mode?
|
723
|
-
def strict?
|
724
|
-
@strict
|
725
|
-
end
|
726
|
-
|
727
|
-
# Record an expectation for receiving the method +sym+ with the
|
728
|
-
# given arguments.
|
729
|
-
def method_missing(sym, *args, &block)
|
730
|
-
expectation = @mock.should_receive(sym).and_return(&block)
|
731
|
-
if strict?
|
732
|
-
args = args.collect { |arg| eq(arg) }
|
733
|
-
expectation.with(*args).ordered.once
|
734
|
-
else
|
735
|
-
expectation.with(*args)
|
736
|
-
end
|
737
|
-
expectation
|
738
|
-
end
|
739
|
-
end
|
740
|
-
|
741
|
-
####################################################################
|
742
|
-
# Test::Unit::TestCase Integration.
|
743
|
-
#
|
744
|
-
# Include this module in any TestCase class in a Test::Unit test
|
745
|
-
# suite to get integration with FlexMock. When this module is
|
746
|
-
# included, mocks may be created with a simple call to the
|
747
|
-
# +flexmock+ method. Mocks created with via the method call will
|
748
|
-
# automatically be verified in the teardown of the test case.
|
749
|
-
#
|
750
|
-
# <b>Note:</b> If you define a +teardown+ method in the test case,
|
751
|
-
# <em>dont' forget to invoke the +super+ method!</em> Failure to
|
752
|
-
# invoke super will cause all mocks to not be verified.
|
753
|
-
#
|
754
|
-
module TestCase
|
755
|
-
include ArgumentTypes
|
756
|
-
|
757
|
-
# Teardown the test case, verifying any mocks that might have been
|
758
|
-
# defined in this test case.
|
759
|
-
def teardown
|
760
|
-
super
|
761
|
-
flexmock_teardown
|
762
|
-
end
|
763
|
-
|
764
|
-
# Do the flexmock specific teardown stuff.
|
765
|
-
def flexmock_teardown
|
766
|
-
@flexmock_created_mocks ||= []
|
767
|
-
if passed?
|
768
|
-
@flexmock_created_mocks.each do |m|
|
769
|
-
m.mock_verify
|
770
|
-
end
|
771
|
-
end
|
772
|
-
ensure
|
773
|
-
@flexmock_created_mocks.each do |m|
|
774
|
-
m.mock_teardown
|
775
|
-
end
|
776
|
-
@flexmock_created_mocks = []
|
777
|
-
@flexmock_interceptors ||= []
|
778
|
-
@flexmock_interceptors.each do |i|
|
779
|
-
i.restore
|
780
|
-
end
|
781
|
-
end
|
782
|
-
|
783
|
-
# Create a FlexMock object with the given name. Mocks created
|
784
|
-
# with this method will be automatically verify during teardown
|
785
|
-
# (assuming the the flexmock teardown isn't overridden).
|
786
|
-
#
|
787
|
-
# If a block is given, then the mock object is passed to the block and
|
788
|
-
# may be configured within the block.
|
789
|
-
def flexmock(name="unknown")
|
790
|
-
mock = FlexMock.new(name)
|
791
|
-
yield(mock) if block_given?
|
792
|
-
flexmock_remember(mock)
|
793
|
-
mock
|
794
|
-
end
|
795
|
-
|
796
|
-
# Stub the given object by overriding the behavior of individual
|
797
|
-
# methods. The stub object returned will respond to the
|
798
|
-
# +should_receive+ method, just like normal stubs.
|
799
|
-
#
|
800
|
-
# If a block is given, then the stub object is passed to the block
|
801
|
-
# and may be configured within the block.
|
802
|
-
#
|
803
|
-
# Example: Stub out DBI to return a fake db connection.
|
804
|
-
#
|
805
|
-
# flexstub(DBI) do |s|
|
806
|
-
# s.should_receive(:connect).and_return {
|
807
|
-
# flexmock("db connection") do |m|
|
808
|
-
# m.should_receive(:select_all).and_return(...)
|
809
|
-
# end
|
810
|
-
# }
|
811
|
-
# end
|
812
|
-
#
|
813
|
-
def flexstub(obj, name=nil)
|
814
|
-
name ||= "flexstub(#{obj.class.to_s})"
|
815
|
-
obj.instance_eval {
|
816
|
-
@flexmock_proxy ||= StubProxy.new(obj, FlexMock.new(name))
|
817
|
-
}
|
818
|
-
proxy = obj.instance_variable_get("@flexmock_proxy")
|
819
|
-
yield(proxy) if block_given?
|
820
|
-
flexmock_remember(proxy)
|
821
|
-
end
|
822
|
-
|
823
|
-
# Intercept the named class in the target class for the duration
|
824
|
-
# of the test. Class interception is very simple-minded and has a
|
825
|
-
# number of restrictions. First, the intercepted class must be
|
826
|
-
# reference in the tested class via a simple constant name
|
827
|
-
# (e.g. no scoped names using "::") that is not directly defined
|
828
|
-
# in the class itself. After the test, a proxy class constant
|
829
|
-
# will be left behind that will forward all calls to the original
|
830
|
-
# class.
|
831
|
-
#
|
832
|
-
# <b>Warning:</b> <em>Class Interception is deprecated. Use
|
833
|
-
# flexstub instead.</em>
|
834
|
-
#
|
835
|
-
# Usage:
|
836
|
-
# intercept(SomeClass).in(ClassBeingTested).with(MockClass)
|
837
|
-
# intercept(SomeClass).with(MockClass).in(ClassBeingTested)
|
838
|
-
#
|
839
|
-
def intercept(intercepted_class)
|
840
|
-
result = Interception.new(intercepted_class)
|
841
|
-
@flexmock_interceptors ||= []
|
842
|
-
@flexmock_interceptors << result
|
843
|
-
result
|
844
|
-
end
|
845
|
-
|
846
|
-
private
|
847
|
-
|
848
|
-
def flexmock_remember(mocking_object)
|
849
|
-
@flexmock_created_mocks ||= []
|
850
|
-
@flexmock_created_mocks << mocking_object
|
851
|
-
mocking_object.mock_container = self
|
852
|
-
mocking_object
|
853
|
-
end
|
854
|
-
end
|
855
|
-
|
856
|
-
####################################################################
|
857
|
-
# A Class Interception defines a constant in the target class to be
|
858
|
-
# a proxy that points to a replacement class for the duration of a
|
859
|
-
# test. When an interception is restored, the proxy will point to
|
860
|
-
# the original intercepted class.
|
861
|
-
#
|
862
|
-
class Interception
|
863
|
-
# Create an interception object with the class to intercepted.
|
864
|
-
def initialize(intercepted_class)
|
865
|
-
@intercepted = nil
|
866
|
-
@target = nil
|
867
|
-
@replacement = nil
|
868
|
-
@proxy = nil
|
869
|
-
intercept(intercepted_class)
|
870
|
-
update
|
871
|
-
end
|
872
|
-
|
873
|
-
# Intercept this class in the class to be tested.
|
874
|
-
def intercept(intercepted_class)
|
875
|
-
self.class.show_intercept_warning
|
876
|
-
@intercepted = intercepted_class
|
877
|
-
update
|
878
|
-
self
|
879
|
-
end
|
880
|
-
|
881
|
-
# Define the class number test that will receive the
|
882
|
-
# interceptioned definition.
|
883
|
-
def in(target_class)
|
884
|
-
@target = target_class
|
885
|
-
update
|
886
|
-
self
|
887
|
-
end
|
888
|
-
|
889
|
-
# Define the replacement class. This is normally a proxy or a
|
890
|
-
# stub.
|
891
|
-
def with(replacement_class)
|
892
|
-
@replacement = replacement_class
|
893
|
-
update
|
894
|
-
self
|
895
|
-
end
|
896
|
-
|
897
|
-
# Restore the original class. The proxy remains in place however.
|
898
|
-
def restore
|
899
|
-
@proxy.proxied_class = @restore_class if @proxy
|
900
|
-
end
|
901
|
-
|
902
|
-
private
|
903
|
-
|
904
|
-
# Update the interception if the definition is complete.
|
905
|
-
def update
|
906
|
-
if complete?
|
907
|
-
do_interception
|
908
|
-
end
|
909
|
-
end
|
910
|
-
|
911
|
-
# Is the interception definition complete. In other words, are
|
912
|
-
# all three actors defined?
|
913
|
-
def complete?
|
914
|
-
@intercepted && @target && @replacement
|
915
|
-
end
|
916
|
-
|
917
|
-
# Implement interception on the classes defined.
|
918
|
-
def do_interception
|
919
|
-
@target_class = coerce_class(@target, "target")
|
920
|
-
@replacement_class = coerce_class(@replacement, "replacement")
|
921
|
-
case @intercepted
|
922
|
-
when String, Symbol
|
923
|
-
@intercepted_name = @intercepted.to_s
|
924
|
-
when Class
|
925
|
-
@intercepted_name = @intercepted.name
|
926
|
-
end
|
927
|
-
@intercepted_class = coerce_class(@intercepted, "intercepted")
|
928
|
-
current_class = @target_class.const_get(@intercepted_name)
|
929
|
-
if ClassProxy === current_class
|
930
|
-
@proxy = current_class
|
931
|
-
@restore_class = @proxy.proxied_class
|
932
|
-
@proxy.proxied_class = @replacement_class
|
933
|
-
else
|
934
|
-
@proxy = ClassProxy.new(@replacement_class)
|
935
|
-
@restore_class = current_class
|
936
|
-
@target_class.const_set(@intercepted_name, @proxy)
|
937
|
-
end
|
938
|
-
end
|
939
|
-
|
940
|
-
# Coerce a class object, string to symbol to be the class object.
|
941
|
-
def coerce_class(klass, where)
|
942
|
-
case klass
|
943
|
-
when String, Symbol
|
944
|
-
lookup_const(klass.to_s, where)
|
945
|
-
else
|
946
|
-
klass
|
947
|
-
end
|
948
|
-
end
|
949
|
-
|
950
|
-
def lookup_const(name, where, target=Object)
|
951
|
-
begin
|
952
|
-
target.const_get(name)
|
953
|
-
rescue NameError
|
954
|
-
raise BadInterceptionError, "in #{where} class #{name}"
|
955
|
-
end
|
956
|
-
end
|
957
|
-
|
958
|
-
@shown_warning = false
|
959
|
-
|
960
|
-
class << self
|
961
|
-
def show_intercept_warning
|
962
|
-
unless @shown_warning
|
963
|
-
@shown_warning = true
|
964
|
-
$stderr.puts "FlexMock Class Interception is deprecated and will be removed in future versions."
|
965
|
-
end
|
966
|
-
end
|
967
|
-
end
|
968
|
-
end
|
969
|
-
|
970
|
-
####################################################################
|
971
|
-
# Class Proxy for class interception. Forward all method calls to
|
972
|
-
# whatever is the proxied_class.
|
973
|
-
#
|
974
|
-
class ClassProxy
|
975
|
-
attr_accessor :proxied_class
|
976
|
-
def initialize(default_class)
|
977
|
-
@proxied_class = default_class
|
978
|
-
end
|
979
|
-
def method_missing(sym, *args, &block)
|
980
|
-
@proxied_class.__send__(sym, *args, &block)
|
981
|
-
end
|
982
|
-
end
|
983
|
-
|
984
|
-
####################################################################
|
985
|
-
# StubProxy is used to mate the mock framework to an existing
|
986
|
-
# object. The object is "enhanced" with a reference to a mock
|
987
|
-
# object (stored in <tt>@flexmock_mock</tt>). When the
|
988
|
-
# +should_receive+ method is sent to the proxy, it overrides the
|
989
|
-
# existing object's method by creating singleton method that
|
990
|
-
# forwards to the mock. When testing is complete, StubProxy
|
991
|
-
# will erase the mocking infrastructure from the object being
|
992
|
-
# stubbed (e.g. remove instance variables and mock singleton
|
993
|
-
# methods).
|
994
|
-
#
|
995
|
-
class StubProxy
|
996
|
-
attr_reader :mock
|
997
|
-
|
998
|
-
# Initialize a StubProxy object.
|
999
|
-
def initialize(obj, mock)
|
1000
|
-
@obj = obj
|
1001
|
-
@mock = mock
|
1002
|
-
@method_definitions = {}
|
1003
|
-
@methods_proxied = []
|
1004
|
-
end
|
1005
|
-
|
1006
|
-
# Stub out the given method in the existing object and then let the
|
1007
|
-
# mock object handle should_receive.
|
1008
|
-
def should_receive(method_name)
|
1009
|
-
method_name = method_name.to_sym
|
1010
|
-
unless @methods_proxied.include?(method_name)
|
1011
|
-
hide_existing_method(method_name)
|
1012
|
-
@methods_proxied << method_name
|
1013
|
-
end
|
1014
|
-
@mock.should_receive(method_name)
|
1015
|
-
end
|
1016
|
-
|
1017
|
-
# new_instances is a short cut method for overriding the behavior of any
|
1018
|
-
# new instances created via a stubbed class object.
|
1019
|
-
#
|
1020
|
-
# By default, new_instances will stub the behaviour of the :new and
|
1021
|
-
# :allocate methods. If you wish to stub a different set of class
|
1022
|
-
# methods, just pass a list of symbols to as arguments.
|
1023
|
-
#
|
1024
|
-
# For example, to stub only objects created by :make (and not :new
|
1025
|
-
# or :allocate), use:
|
1026
|
-
#
|
1027
|
-
# flexstub(ClassName).new_instances(:make) do |obj|
|
1028
|
-
# obj.should_receive(...)
|
1029
|
-
# end
|
1030
|
-
#
|
1031
|
-
def new_instances(*allocators, &block)
|
1032
|
-
fail ArgumentError, "new_instances requires a Class to stub" unless Class === @obj
|
1033
|
-
fail ArgumentError, "new_instances requires a block" unless block_given?
|
1034
|
-
allocators = [:new, :allocate] if allocators.empty?
|
1035
|
-
allocators.each do |m|
|
1036
|
-
self.should_receive(m).and_return { |*args|
|
1037
|
-
new_obj = invoke_original(m, args)
|
1038
|
-
mock = mock_container.flexstub(new_obj)
|
1039
|
-
block.call(mock)
|
1040
|
-
new_obj
|
1041
|
-
}
|
1042
|
-
end
|
1043
|
-
nil
|
1044
|
-
end
|
1045
|
-
|
1046
|
-
# any_instance is present for backwards compatibility with version 0.5.0.
|
1047
|
-
# @deprecated
|
1048
|
-
def any_instance(&block)
|
1049
|
-
$stderr.puts "any_instance is deprecated, use new_instances instead."
|
1050
|
-
new_instances(&block)
|
1051
|
-
end
|
1052
|
-
|
1053
|
-
# Invoke the original definition of method on the object supported by
|
1054
|
-
# the stub.
|
1055
|
-
def invoke_original(method, args)
|
1056
|
-
method_proc = @method_definitions[method]
|
1057
|
-
method_proc.call(*args)
|
1058
|
-
end
|
1059
|
-
private :invoke_original
|
1060
|
-
|
1061
|
-
# Verify that the mock has been properly called. After verification,
|
1062
|
-
# detach the mocking infrastructure from the existing object.
|
1063
|
-
def mock_verify
|
1064
|
-
@mock.mock_verify
|
1065
|
-
end
|
1066
|
-
|
1067
|
-
# Remove all traces of the mocking framework from the existing object.
|
1068
|
-
def mock_teardown
|
1069
|
-
if ! detached?
|
1070
|
-
@methods_proxied.each do |method_name|
|
1071
|
-
remove_current_method(method_name)
|
1072
|
-
restore_original_definition(method_name)
|
1073
|
-
end
|
1074
|
-
@obj.instance_variable_set("@flexmock_proxy", nil)
|
1075
|
-
@obj = nil
|
1076
|
-
end
|
1077
|
-
end
|
1078
|
-
|
1079
|
-
# Return the container for this mocking object. Returns nil if the
|
1080
|
-
# mock is not in a container. Mock containers make sure that mock objects
|
1081
|
-
# inside the container are torn down at the end of a test
|
1082
|
-
def mock_container
|
1083
|
-
@mock.mock_container
|
1084
|
-
end
|
1085
|
-
|
1086
|
-
# Set the container for this mock object.
|
1087
|
-
def mock_container=(container)
|
1088
|
-
@mock.mock_container = container
|
1089
|
-
end
|
1090
|
-
|
1091
|
-
private
|
1092
|
-
|
1093
|
-
# The singleton class of the object.
|
1094
|
-
def sclass
|
1095
|
-
class << @obj; self; end
|
1096
|
-
end
|
1097
|
-
|
1098
|
-
# Is the current method a singleton method in the object we are
|
1099
|
-
# mocking?
|
1100
|
-
def singleton?(method_name)
|
1101
|
-
@obj.methods(false).include?(method_name.to_s)
|
1102
|
-
end
|
1103
|
-
|
1104
|
-
# Hide the existing method definition with a singleton defintion
|
1105
|
-
# that proxies to our mock object. If the current definition is a
|
1106
|
-
# singleton, we need to record the definition and remove it before
|
1107
|
-
# creating our own singleton method. If the current definition is
|
1108
|
-
# not a singleton, all we need to do is override it with our own
|
1109
|
-
# singleton.
|
1110
|
-
def hide_existing_method(method_name)
|
1111
|
-
if @obj.respond_to?(method_name)
|
1112
|
-
new_alias = new_name(method_name)
|
1113
|
-
unless @obj.respond_to?(new_alias)
|
1114
|
-
sclass.class_eval do
|
1115
|
-
alias_method(new_alias, method_name)
|
1116
|
-
end
|
1117
|
-
end
|
1118
|
-
my_object = @obj
|
1119
|
-
@method_definitions[method_name] = Proc.new { |*args|
|
1120
|
-
block = nil
|
1121
|
-
if Proc === args.last
|
1122
|
-
block = args.last
|
1123
|
-
args = args[0...-1]
|
1124
|
-
end
|
1125
|
-
my_object.send(new_alias, *args, &block)
|
1126
|
-
}
|
1127
|
-
end
|
1128
|
-
remove_current_method(method_name) if singleton?(method_name)
|
1129
|
-
define_proxy_method(method_name)
|
1130
|
-
end
|
1131
|
-
|
1132
|
-
# Define a proxy method that forwards to our mock object. The
|
1133
|
-
# proxy method is defined as a singleton method on the object
|
1134
|
-
# being mocked.
|
1135
|
-
def define_proxy_method(method_name)
|
1136
|
-
sclass.class_eval %{
|
1137
|
-
def #{method_name}(*args, &block) @flexmock_proxy.mock.#{method_name}(*args, &block) end
|
1138
|
-
}
|
1139
|
-
end
|
1140
|
-
|
1141
|
-
# Restore the original singleton defintion for method_name that
|
1142
|
-
# was saved earlier.
|
1143
|
-
def restore_original_definition(method_name)
|
1144
|
-
method_def = @method_definitions[method_name]
|
1145
|
-
if method_def
|
1146
|
-
the_alias = new_name(method_name)
|
1147
|
-
sclass.class_eval do
|
1148
|
-
alias_method(method_name, the_alias)
|
1149
|
-
end
|
1150
|
-
end
|
1151
|
-
end
|
1152
|
-
|
1153
|
-
# Remove the current method if it is a singleton method of the
|
1154
|
-
# object being mocked.
|
1155
|
-
def remove_current_method(method_name)
|
1156
|
-
sclass.class_eval { remove_method(method_name) }
|
1157
|
-
end
|
1158
|
-
|
1159
|
-
# Have we been detached from the existing object?
|
1160
|
-
def detached?
|
1161
|
-
@obj.nil?
|
1162
|
-
end
|
1163
|
-
|
1164
|
-
# Generate a name to be used to alias the original behavior.
|
1165
|
-
def new_name(old_name)
|
1166
|
-
"flexmock_original_behavior_for_#{old_name}"
|
1167
|
-
end
|
1168
|
-
|
1169
|
-
end
|
1170
|
-
end
|
12
|
+
require 'flexmock/base'
|
13
|
+
require 'flexmock/test_unit_integration'
|