test-unit-mock 0.30
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +58 -0
- data/ChangeLog +262 -0
- data/README +203 -0
- data/install.rb +85 -0
- data/misc/readmecode.rb +125 -0
- data/mock.rb +467 -0
- data/test-unit-mock.gemspec +20 -0
- data/test.rb +327 -0
- data/utils.rb +345 -0
- metadata +59 -0
data/install.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
#
|
3
|
+
# $Date: 2003/03/04 23:33:31 $
|
4
|
+
# Copyright (c) 2000 Masatoshi SEKI
|
5
|
+
#
|
6
|
+
# install.rb is copyrighted free software by Masatoshi SEKI.
|
7
|
+
# You can redistribute it and/or modify it under the same term as Ruby.
|
8
|
+
|
9
|
+
require 'rbconfig'
|
10
|
+
require 'find'
|
11
|
+
require 'ftools'
|
12
|
+
|
13
|
+
include Config
|
14
|
+
|
15
|
+
class Installer
|
16
|
+
protected
|
17
|
+
def install(from, to, mode = nil, verbose = false)
|
18
|
+
str = "install '#{from}' to '#{to}'"
|
19
|
+
str += ", mode=#{mode}" if mode
|
20
|
+
puts str if verbose
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
def makedirs(*dirs)
|
25
|
+
for d in dirs
|
26
|
+
puts "mkdir #{d}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(test=false)
|
31
|
+
@version = CONFIG["MAJOR"]+"."+CONFIG["MINOR"]
|
32
|
+
@libdir = File.join(CONFIG["libdir"], "ruby", @version)
|
33
|
+
@sitelib = find_site_libdir
|
34
|
+
@ftools = (test) ? self : File
|
35
|
+
end
|
36
|
+
public
|
37
|
+
attr_reader(:libdir, :sitelib)
|
38
|
+
|
39
|
+
private
|
40
|
+
def find_site_libdir
|
41
|
+
site_libdir = $:.find {|x| x =~ /site_ruby$/}
|
42
|
+
if !site_libdir
|
43
|
+
site_libdir = File.join(@libdir, "site_ruby")
|
44
|
+
elsif site_libdir !~ Regexp.quote(@version)
|
45
|
+
site_libdir = File.join(site_libdir, @version)
|
46
|
+
end
|
47
|
+
site_libdir
|
48
|
+
end
|
49
|
+
|
50
|
+
public
|
51
|
+
def files_in_dir(dir)
|
52
|
+
list = []
|
53
|
+
Find.find(dir) do |f|
|
54
|
+
list.push(f)
|
55
|
+
end
|
56
|
+
list
|
57
|
+
end
|
58
|
+
|
59
|
+
public
|
60
|
+
def install_files(srcdir, files, destdir=@sitelib)
|
61
|
+
path = []
|
62
|
+
dir = []
|
63
|
+
|
64
|
+
for f in files
|
65
|
+
next if (f = f[srcdir.length+1..-1]) == nil
|
66
|
+
path.push f if File.ftype(File.join(srcdir, f)) == 'file'
|
67
|
+
dir |= [ File.dirname(File.join(destdir, f)) ]
|
68
|
+
end
|
69
|
+
@ftools.makedirs(*dir)
|
70
|
+
for f in path
|
71
|
+
@ftools.install(File.join(srcdir, f), File.join(destdir, f), nil, true)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
public
|
76
|
+
def install_rb
|
77
|
+
intall_files('lib', files_in_dir('lib'))
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
if __FILE__ == $0
|
82
|
+
inst = Installer.new(ARGV.shift == '-n')
|
83
|
+
inst.install_files( '.', ['./mock.rb'], File::join(inst.sitelib, 'test/unit') )
|
84
|
+
end
|
85
|
+
|
data/misc/readmecode.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift ".", ".."
|
4
|
+
|
5
|
+
require File::join( File::dirname(File::dirname( __FILE__ )), 'utils.rb' )
|
6
|
+
|
7
|
+
begin
|
8
|
+
require 'mock'
|
9
|
+
rescue LoadError
|
10
|
+
require 'test/unit/mock'
|
11
|
+
end
|
12
|
+
require 'socket'
|
13
|
+
require 'test/unit'
|
14
|
+
|
15
|
+
# This is just a test to make sure the code in the README works as advertised.
|
16
|
+
|
17
|
+
class MockTestExperiment < Test::Unit::TestCase
|
18
|
+
|
19
|
+
def setup
|
20
|
+
# Create the mock object
|
21
|
+
@mockSocket = Test::Unit::MockObject( TCPSocket ).new
|
22
|
+
|
23
|
+
# Make the #addr method return three cycling values (which will be repeated
|
24
|
+
# when it reaches the end
|
25
|
+
@mockSocket.setReturnValues( :addr => [
|
26
|
+
["AF_INET", 23, "localhost", "127.0.0.1"],
|
27
|
+
["AF_INET", 80, "slashdot.org", "66.35.250.150"],
|
28
|
+
["AF_INET", 2401, "helium.ruby-lang.org", "210.251.121.214"],
|
29
|
+
] )
|
30
|
+
|
31
|
+
# Make the #write and #read methods call a Proc and a Method, respectively
|
32
|
+
@mockSocket.setReturnValues( :write => Proc::new {|str| str.length},
|
33
|
+
:read => method(:fakeRead) )
|
34
|
+
|
35
|
+
# Set up the #getsockopt method to return a value based on the arguments
|
36
|
+
# given:
|
37
|
+
@mockSocket.setReturnValues( :getsockopt => {
|
38
|
+
[Socket::SOL_TCP, Socket::TCP_NODELAY] => [0].pack("i_"),
|
39
|
+
[Socket::SOL_SOCKET, Socket::SO_REUSEADDR] => [1].pack("i_"),
|
40
|
+
} )
|
41
|
+
|
42
|
+
@mockSocket.setCallOrder( :addr, :getsockopt, :write, :read, :write, :read )
|
43
|
+
@mockSocket.strictCallOrder = true
|
44
|
+
end
|
45
|
+
alias :set_up :setup
|
46
|
+
|
47
|
+
def teardown
|
48
|
+
@mockSocket = nil
|
49
|
+
end
|
50
|
+
alias :tear_down :teardown
|
51
|
+
|
52
|
+
|
53
|
+
# A method to fake reading from the socket.
|
54
|
+
def fakeRead( len )
|
55
|
+
return "x" * len
|
56
|
+
end
|
57
|
+
|
58
|
+
# This should pass
|
59
|
+
def test_correctorder
|
60
|
+
@mockSocket.activate
|
61
|
+
|
62
|
+
# Call the methods in the correct order
|
63
|
+
assert_nothing_raised {
|
64
|
+
@mockSocket.addr
|
65
|
+
@mockSocket.getsockopt( Socket::SOL_TCP, Socket::TCP_NODELAY )
|
66
|
+
@mockSocket.write( "foo" )
|
67
|
+
@mockSocket.read( 1024 )
|
68
|
+
@mockSocket.write( "bar" )
|
69
|
+
@mockSocket.read( 4096 )
|
70
|
+
}
|
71
|
+
|
72
|
+
if $DEBUG
|
73
|
+
puts "Call trace:\n " + @mockSocket.callTrace.join("\n ")
|
74
|
+
end
|
75
|
+
|
76
|
+
# Check method call order on the mocked socket
|
77
|
+
@mockSocket.verify
|
78
|
+
end
|
79
|
+
|
80
|
+
# This should fail with an incorrect order message
|
81
|
+
def test_incorrectorder
|
82
|
+
@mockSocket.activate
|
83
|
+
|
84
|
+
# Call the methods in the correct order
|
85
|
+
assert_nothing_raised {
|
86
|
+
@mockSocket.addr
|
87
|
+
@mockSocket.getsockopt( Socket::SOL_TCP, Socket::TCP_NODELAY )
|
88
|
+
@mockSocket.read( 1024 )
|
89
|
+
@mockSocket.write( "foo" )
|
90
|
+
@mockSocket.write( "bar" )
|
91
|
+
@mockSocket.read( 4096 )
|
92
|
+
}
|
93
|
+
|
94
|
+
if $DEBUG
|
95
|
+
puts "Call trace:\n " + @mockSocket.callTrace.join("\n ")
|
96
|
+
end
|
97
|
+
|
98
|
+
# Check method call order on the mocked socket
|
99
|
+
@mockSocket.verify
|
100
|
+
end
|
101
|
+
|
102
|
+
# This should fail with a 'missing' message
|
103
|
+
def test_missingcall
|
104
|
+
@mockSocket.activate
|
105
|
+
|
106
|
+
# Call the methods in the correct order
|
107
|
+
assert_nothing_raised {
|
108
|
+
@mockSocket.addr
|
109
|
+
@mockSocket.getsockopt( Socket::SOL_TCP, Socket::TCP_NODELAY )
|
110
|
+
@mockSocket.write( "foo" )
|
111
|
+
@mockSocket.read( 1024 )
|
112
|
+
@mockSocket.write( "bar" )
|
113
|
+
}
|
114
|
+
|
115
|
+
if $DEBUG
|
116
|
+
puts "Call trace:\n " + @mockSocket.callTrace.join("\n ")
|
117
|
+
end
|
118
|
+
|
119
|
+
# Check method call order on the mocked socket
|
120
|
+
@mockSocket.verify
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
|
data/mock.rb
ADDED
@@ -0,0 +1,467 @@
|
|
1
|
+
# Ruby/Mock version 1.0
|
2
|
+
#
|
3
|
+
# A class for conveniently building mock objects in Test::Unit test cases. It is
|
4
|
+
# based on ideas in Ruby/Mock by Nat Pryce <nat.pryce@b13media.com>, which is a
|
5
|
+
# class for doing much the same thing for RUnit test cases.
|
6
|
+
#
|
7
|
+
# == Examples
|
8
|
+
#
|
9
|
+
# For the examples below, it is assumed that a hypothetical '<tt>Adapter</tt>'
|
10
|
+
# class is needed to test whatever class is being tested. It has two instance
|
11
|
+
# methods in addition to its initializer: <tt>read</tt>, which takes no
|
12
|
+
# arguments and returns data read from the adapted source, and <tt>write</tt>,
|
13
|
+
# which takes a String and writes as much as it can to the adapted destination,
|
14
|
+
# returning any that is left over.
|
15
|
+
#
|
16
|
+
# # With the in-place mock-object constructor, you can make an instance of a
|
17
|
+
# # one-off anonymous test class:
|
18
|
+
# mockAdapter = Test::Unit::MockObject( Adapter ).new
|
19
|
+
#
|
20
|
+
# # Now set up some return values for the next test:
|
21
|
+
# mockAdapter.setReturnValues( :read => "",
|
22
|
+
# :write => Proc::new {|data| data[-20,20]} )
|
23
|
+
#
|
24
|
+
# # Mandate a certain order to the calls
|
25
|
+
# mockAdapter.setCallOrder( :read, :read, :read, :write, :read )
|
26
|
+
#
|
27
|
+
# # Now start the mock object recording interactions with it
|
28
|
+
# mockAdapter.activate
|
29
|
+
#
|
30
|
+
# # Send the adapter to the tested object and run the tests
|
31
|
+
# testedObject.setAdapter( mockAdapter )
|
32
|
+
# ...
|
33
|
+
#
|
34
|
+
# # Now check the order of method calls on the mock object against the expected
|
35
|
+
# # order.
|
36
|
+
# mockAdapter.verify
|
37
|
+
#
|
38
|
+
# If you require more advanced functionality in your mock class, you can also
|
39
|
+
# use the anonymous class returned by the Test::Unit::MockObject factory method
|
40
|
+
# as the superclass for your own mockup like this:
|
41
|
+
#
|
42
|
+
# # Create a mocked adapter class
|
43
|
+
# class MockAdapter < Test::Unit::MockObject( Adapter )
|
44
|
+
#
|
45
|
+
# def initialize
|
46
|
+
# super
|
47
|
+
# setCallOrder( :read, :read, :read, :write, :read )
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# def read( *args )
|
51
|
+
# @readargs = args
|
52
|
+
# super # Call the mocked method to record the call
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# def write( *args )
|
56
|
+
# @writeargs = args
|
57
|
+
# super # Call the mocked method to record the call
|
58
|
+
# end
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# == Note
|
62
|
+
#
|
63
|
+
# All the testing and setup methods in the Test::Unit::Mockup class (the
|
64
|
+
# abstract class that new MockObjects inherit from) have two aliases for your
|
65
|
+
# convenience:
|
66
|
+
#
|
67
|
+
# * Each one has a double-underscore alias so that methods which collide with
|
68
|
+
# them from the mocked class don't obscure them. Eg.,
|
69
|
+
#
|
70
|
+
# mockAdapter.__setCallOrder( :read, :read, :read, :write, :read )
|
71
|
+
#
|
72
|
+
# can be used instead of the call from the example above if the Adapter for
|
73
|
+
# some reason already has a 'setCallOrder' instance method.
|
74
|
+
#
|
75
|
+
# * Non-camelCase versions of the methods are also provided. Eg.,
|
76
|
+
#
|
77
|
+
# mockAdapter.set_call_order( :read, :read, :read, :write, :read )
|
78
|
+
#
|
79
|
+
# will work, too. Double-underscored *and* non camelCased aliases are not
|
80
|
+
# defined; if anyone complains, I'll add them.
|
81
|
+
#
|
82
|
+
# == Rcsid
|
83
|
+
#
|
84
|
+
# $Id: mock.rb,v 1.10 2003/10/01 15:11:40 deveiant Exp $
|
85
|
+
#
|
86
|
+
# == Authors
|
87
|
+
#
|
88
|
+
# * Michael Granger <ged@FaerieMUD.org>
|
89
|
+
#
|
90
|
+
#
|
91
|
+
#
|
92
|
+
|
93
|
+
require 'algorithm/diff'
|
94
|
+
|
95
|
+
require 'test/unit'
|
96
|
+
require 'test/unit/assertions'
|
97
|
+
|
98
|
+
module Test
|
99
|
+
module Unit
|
100
|
+
|
101
|
+
# The Regexp that matches methods which will not be mocked.
|
102
|
+
UnmockedMethods = %r{^(
|
103
|
+
__ # __id__, __call__, etc.
|
104
|
+
|inspect # Useful as-is for debugging, and hard to fake
|
105
|
+
|kind_of\?|is_a\?|instance_of\? # / Can't fake simply -- delegated to the
|
106
|
+
|type|class # \ actual underlying class
|
107
|
+
|method|send|respond_to\? # These will work fine as-is
|
108
|
+
|hash # There's no good way to fake this
|
109
|
+
)}x
|
110
|
+
|
111
|
+
### An abstract base class that provides the setup and testing methods
|
112
|
+
### to concrete mock objects.
|
113
|
+
class Mockup
|
114
|
+
|
115
|
+
include Test::Unit::Assertions
|
116
|
+
|
117
|
+
### Instantiate and return a new mock object after recording the
|
118
|
+
### specified args.
|
119
|
+
def initialize( *args )
|
120
|
+
@args = args
|
121
|
+
@calls = []
|
122
|
+
@activated = nil
|
123
|
+
@returnValues = Hash::new( true )
|
124
|
+
@callOrder = []
|
125
|
+
@strictCallOrder = false
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
#########################################################
|
130
|
+
### F A K E T Y P E - C H E C K I N G M E T H O D S
|
131
|
+
#########################################################
|
132
|
+
|
133
|
+
# Handle #type and #class methods
|
134
|
+
alias :__class :class
|
135
|
+
def class # :nodoc:
|
136
|
+
return __class.mockedClass
|
137
|
+
end
|
138
|
+
undef_method :type
|
139
|
+
alias_method :type, :class
|
140
|
+
|
141
|
+
# Fake instance_of?, kind_of?, and is_a? with the mocked class
|
142
|
+
def instance_of?( klass ) # :nodoc:
|
143
|
+
self.class == klass
|
144
|
+
end
|
145
|
+
def kind_of?( klass ) # :nodoc:
|
146
|
+
self.class <= klass
|
147
|
+
end
|
148
|
+
alias_method :is_a?, :kind_of?
|
149
|
+
|
150
|
+
|
151
|
+
#########################################################
|
152
|
+
### S E T U P M E T H O D S
|
153
|
+
#########################################################
|
154
|
+
|
155
|
+
### Set the return value for one or more methods. The <tt>hash</tt>
|
156
|
+
### should contain one or more key/value pairs, the key of which is
|
157
|
+
### the symbol which corresponds to the method being configured, and
|
158
|
+
### the value which is a specification of the value to be
|
159
|
+
### returned. More complex returns can be configured with one the
|
160
|
+
### following types of values:
|
161
|
+
###
|
162
|
+
### [<tt>Method</tt> or <tt>Proc</tt>]
|
163
|
+
### A <tt>Method</tt> or <tt>Proc</tt> object will be called with
|
164
|
+
### the arguments given to the method, and whatever it returns
|
165
|
+
### will be used as the return value.
|
166
|
+
### [<tt>Array</tt>]
|
167
|
+
### The first value in the <tt>Array</tt> will be rotated to the
|
168
|
+
### end of the Array and returned.
|
169
|
+
### [<tt>Hash</tt>]
|
170
|
+
### The Array of method arguments will be used as a key, and
|
171
|
+
### whatever the corresponding value in the given Hash is will be
|
172
|
+
### returned.
|
173
|
+
###
|
174
|
+
### Any other value will be returned as-is. To return one of the
|
175
|
+
### above types of objects literally, just wrap it in an Array like
|
176
|
+
### so:
|
177
|
+
###
|
178
|
+
### # Return a literal Proc without calling it:
|
179
|
+
### mockObj.setReturnValues( :meth => [myProc] )
|
180
|
+
###
|
181
|
+
### # Return a literal Array:
|
182
|
+
### mockObj.setReturnValues( :meth => [["an", "array", "of", "stuff"]] )
|
183
|
+
def setReturnValues( hash )
|
184
|
+
@returnValues.update hash
|
185
|
+
end
|
186
|
+
alias_method :__setReturnValues, :setReturnValues
|
187
|
+
alias_method :set_return_values, :setReturnValues
|
188
|
+
|
189
|
+
|
190
|
+
### Set up an expected method call order and argument specification
|
191
|
+
### to be checked when #verify is called to the methods specified by
|
192
|
+
### the given <tt>symbols</tt>.
|
193
|
+
def setCallOrder( *symbols )
|
194
|
+
@callOrder = symbols
|
195
|
+
end
|
196
|
+
alias_method :__setCallOrder, :setCallOrder
|
197
|
+
alias_method :set_call_order, :setCallOrder
|
198
|
+
|
199
|
+
|
200
|
+
### Set the strict call order flag. When #verify is called, the
|
201
|
+
### methods specified in calls to #setCallOrder will be checked
|
202
|
+
### against the actual methods that were called on the object. If
|
203
|
+
### this flag is set to <tt>true</tt>, any deviation (missing,
|
204
|
+
### misordered, or extra calls) results in a failed assertion. If it
|
205
|
+
### is not set, other method calls may be interspersed between the
|
206
|
+
### calls specified without effect, but a missing or misordered
|
207
|
+
### method still fails.
|
208
|
+
def strictCallOrder=( flag )
|
209
|
+
@strictCallOrder = true if flag
|
210
|
+
end
|
211
|
+
alias_method :__strictCallOrder=, :strictCallOrder=
|
212
|
+
alias_method :strict_call_order=, :strictCallOrder=
|
213
|
+
|
214
|
+
|
215
|
+
### Returns true if strict call order checking is enabled.
|
216
|
+
def strictCallOrder?
|
217
|
+
@strictCallOrder
|
218
|
+
end
|
219
|
+
alias_method :__strictCallOrder?, :strictCallOrder?
|
220
|
+
alias_method :strict_call_order?, :strictCallOrder?
|
221
|
+
|
222
|
+
|
223
|
+
#########################################################
|
224
|
+
### T E S T I N G M E T H O D S
|
225
|
+
#########################################################
|
226
|
+
|
227
|
+
### Returns an array of Strings describing, in cronological order,
|
228
|
+
### what method calls were registered with the object.
|
229
|
+
def callTrace
|
230
|
+
return [] unless @activated
|
231
|
+
@calls.collect {|call|
|
232
|
+
"%s( %s ) at %0.5f seconds from %s" % [
|
233
|
+
call[:method].to_s,
|
234
|
+
call[:args].collect {|arg| arg.inspect}.join(","),
|
235
|
+
call[:time] - @activated,
|
236
|
+
call[:caller][0]
|
237
|
+
]
|
238
|
+
}
|
239
|
+
end
|
240
|
+
alias_method :__callTrace, :callTrace
|
241
|
+
alias_method :call_trace, :callTrace
|
242
|
+
|
243
|
+
|
244
|
+
### Returns an array of Strings describing, in cronological order,
|
245
|
+
### what method calls were registered with the object along with a
|
246
|
+
### full stacktrace for each call.
|
247
|
+
def fullCallTrace
|
248
|
+
return [] unless @activated
|
249
|
+
@calls.collect {|call|
|
250
|
+
"%s( %s ) at %0.5f seconds. Called from %s\n\t%s" % [
|
251
|
+
call[:method].to_s,
|
252
|
+
call[:args].collect {|arg| arg.inspect}.join(","),
|
253
|
+
call[:time] - @activated,
|
254
|
+
call[:caller][0],
|
255
|
+
call[:caller][1..-1].join("\n\t"),
|
256
|
+
]
|
257
|
+
}
|
258
|
+
end
|
259
|
+
alias_method :__fullCallTrace, :fullCallTrace
|
260
|
+
alias_method :full_call_trace, :fullCallTrace
|
261
|
+
|
262
|
+
|
263
|
+
### Turn on call registration -- begin testing.
|
264
|
+
def activate
|
265
|
+
raise "Already activated!" if @activated
|
266
|
+
self.__clear
|
267
|
+
@activated = Time::now
|
268
|
+
end
|
269
|
+
alias_method :__activate, :activate
|
270
|
+
|
271
|
+
|
272
|
+
### Verify the registered required methods were called with the
|
273
|
+
### specified args
|
274
|
+
def verify
|
275
|
+
raise "Cannot verify a mock object that has never been "\
|
276
|
+
"activated." unless @activated
|
277
|
+
return true if @callOrder.empty?
|
278
|
+
|
279
|
+
actualCallOrder = @calls.collect {|call| call[:method]}
|
280
|
+
diff = Diff::diff( @callOrder, actualCallOrder )
|
281
|
+
|
282
|
+
# In strict mode, any differences are failures
|
283
|
+
if @strictCallOrder
|
284
|
+
msg = "{Message}"
|
285
|
+
assert_block( msg ) {
|
286
|
+
unless diff.empty?
|
287
|
+
msg.replace __makeCallOrderFailMsg(*diff[0])
|
288
|
+
end
|
289
|
+
diff.empty?
|
290
|
+
}
|
291
|
+
|
292
|
+
# In non-strict mode, only methods missing (:-) from the call
|
293
|
+
# order are failures.
|
294
|
+
else
|
295
|
+
msg = "{Message}"
|
296
|
+
assert_block( msg ) {
|
297
|
+
missingDiff = diff.find {|d| d[0] == :-}
|
298
|
+
unless missingDiff.nil?
|
299
|
+
msg.replace __makeCallOrderFailMsg( *missingDiff )
|
300
|
+
end
|
301
|
+
missingDiff.nil?
|
302
|
+
}
|
303
|
+
end
|
304
|
+
end
|
305
|
+
alias_method :__verify, :verify
|
306
|
+
|
307
|
+
|
308
|
+
### Deactivate the object without doing call order checks and clear
|
309
|
+
### the call list, but keep its configuration.
|
310
|
+
def clear
|
311
|
+
@calls.clear
|
312
|
+
@activated = nil
|
313
|
+
end
|
314
|
+
alias_method :__clear, :clear
|
315
|
+
|
316
|
+
|
317
|
+
### Clear the call list and call order, unset any return values, and
|
318
|
+
### deactivate the object without checking for conformance to the
|
319
|
+
### call order.
|
320
|
+
def reset
|
321
|
+
self.__clear
|
322
|
+
@callOrder.clear
|
323
|
+
@returnValues.clear
|
324
|
+
end
|
325
|
+
alias_method :__reset, :reset
|
326
|
+
|
327
|
+
|
328
|
+
#########
|
329
|
+
protected
|
330
|
+
#########
|
331
|
+
|
332
|
+
### Register a call to the faked method designated by <tt>sym</tt>
|
333
|
+
### if the object is activated, and return a value as configured by
|
334
|
+
### #setReturnValues given the specified <tt>args</tt>.
|
335
|
+
def __mockRegisterCall( sym, *args, &block )
|
336
|
+
if @activated
|
337
|
+
@calls.push({
|
338
|
+
:method => sym,
|
339
|
+
:args => args,
|
340
|
+
:time => Time::now,
|
341
|
+
:caller => caller(2),
|
342
|
+
})
|
343
|
+
end
|
344
|
+
|
345
|
+
rval = @returnValues[ sym ]
|
346
|
+
case rval
|
347
|
+
when Method, Proc
|
348
|
+
return rval.call( *args, &block )
|
349
|
+
|
350
|
+
when Array
|
351
|
+
return rval.push( rval.shift )[-1]
|
352
|
+
|
353
|
+
when Hash
|
354
|
+
return rval[ args ]
|
355
|
+
|
356
|
+
else
|
357
|
+
return rval
|
358
|
+
end
|
359
|
+
end
|
360
|
+
alias_method :__mock_register_call, :__mockRegisterCall
|
361
|
+
|
362
|
+
|
363
|
+
### Build and return an error message for a call-order verification
|
364
|
+
### failure. The expected arguments are those returned in an element
|
365
|
+
### of the Array that is returned from Diff::diff.
|
366
|
+
def __makeCallOrderFailMsg( action, position, elements )
|
367
|
+
case action
|
368
|
+
|
369
|
+
# "Extra" method/s
|
370
|
+
when :+
|
371
|
+
extraCall = @calls[ position ]
|
372
|
+
return "Call order assertion failed: Unexpected method %s "\
|
373
|
+
"called from %s at %0.5f" %
|
374
|
+
[ extraCall[:method].inspect,
|
375
|
+
extraCall[:caller][0],
|
376
|
+
extraCall[:time] - @activated ]
|
377
|
+
|
378
|
+
when :-
|
379
|
+
# If there is a call in the position specified, it was a
|
380
|
+
# misordered or extra call. If not, there was a missing
|
381
|
+
# call.
|
382
|
+
missingCall = @callOrder[ position ]
|
383
|
+
if extraCall = @calls[ position ]
|
384
|
+
return "Call order assertion failed: Expected call to %s, "\
|
385
|
+
"but got call to %s from %s at %0.5f instead" %
|
386
|
+
[ missingCall.inspect,
|
387
|
+
extraCall[:method].inspect,
|
388
|
+
extraCall[:caller][0],
|
389
|
+
extraCall[:time] - @activated ]
|
390
|
+
else
|
391
|
+
return "Call order assertion failed: Missing call to %s." %
|
392
|
+
missingCall.inspect
|
393
|
+
end
|
394
|
+
|
395
|
+
else
|
396
|
+
return "Unknown diff action '#{action.inspect}'"
|
397
|
+
end
|
398
|
+
end
|
399
|
+
alias_method :__make_call_order_fail_msg, :__makeCallOrderFailMsg
|
400
|
+
|
401
|
+
|
402
|
+
end
|
403
|
+
|
404
|
+
|
405
|
+
### Factory method for creating semi-functional mock objects given the
|
406
|
+
### class which is to be mocked. It looks like a constant for purposes
|
407
|
+
### of syntactic sugar.
|
408
|
+
def self::MockObject( klass )
|
409
|
+
mockup = Class::new( Mockup )
|
410
|
+
mockup.instance_eval do @mockedClass = klass end
|
411
|
+
|
412
|
+
### Provide an accessor to class instance var that holds the class
|
413
|
+
### object we're faking
|
414
|
+
class << mockup
|
415
|
+
|
416
|
+
# The actual class being mocked
|
417
|
+
attr_reader :mockedClass
|
418
|
+
|
419
|
+
### Propagate the mocked class ivar to derivatives so it can be
|
420
|
+
### called like:
|
421
|
+
### class MockFoo < Test::Unit::MockObject( RealClass )
|
422
|
+
def inherited( subclass )
|
423
|
+
mc = self.mockedClass
|
424
|
+
subclass.instance_eval do @mockedClass = mc end
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
# Build method definitions for all the mocked class's instance
|
429
|
+
# methods, as well as those given to it by its superclasses, since
|
430
|
+
# we're not really inheriting from it.
|
431
|
+
imethods = klass.instance_methods(true).collect {|name|
|
432
|
+
next if name =~ UnmockedMethods
|
433
|
+
|
434
|
+
# Figure out the argument list
|
435
|
+
argCount = klass.instance_method( name ).arity
|
436
|
+
optionalArgs = false
|
437
|
+
|
438
|
+
if argCount < 0
|
439
|
+
optionalArgs = true
|
440
|
+
argCount = (argCount+1).abs
|
441
|
+
end
|
442
|
+
|
443
|
+
args = []
|
444
|
+
argCount.times do |n| args << "arg#{n+1}" end
|
445
|
+
args << "*optionalArgs" if optionalArgs
|
446
|
+
|
447
|
+
# Build a method definition. Some methods need special
|
448
|
+
# declarations.
|
449
|
+
case name.intern
|
450
|
+
when :initialize
|
451
|
+
"def initialize( %s ) ; super ; end" % args.join(',')
|
452
|
+
|
453
|
+
else
|
454
|
+
"def %s( %s ) ; self.__mockRegisterCall(%s) ; end" %
|
455
|
+
[ name, args.join(','), [":#{name}", *args].join(',') ]
|
456
|
+
end
|
457
|
+
}
|
458
|
+
|
459
|
+
# Now add the instance methods to the mockup class
|
460
|
+
mockup.class_eval imethods.join( "\n" )
|
461
|
+
return mockup
|
462
|
+
end
|
463
|
+
|
464
|
+
end # module Unit
|
465
|
+
end # module Test
|
466
|
+
|
467
|
+
|