flexmock 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +18 -0
- data/README +214 -0
- data/Rakefile +128 -0
- data/flexmock.blurb +10 -0
- data/install.rb +43 -0
- data/lib/flexmock.rb +485 -0
- data/test/test_example.rb +26 -0
- data/test/test_mock.rb +171 -0
- data/test/test_naming.rb +42 -0
- data/test/test_samples.rb +43 -0
- data/test/test_should_receive.rb +443 -0
- metadata +57 -0
data/CHANGELOG
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
= Changes for FlexMock
|
2
|
+
|
3
|
+
== Preversion 0.0.4
|
4
|
+
|
5
|
+
* Added responds_to? and method support.
|
6
|
+
|
7
|
+
== Version 0.0.3
|
8
|
+
|
9
|
+
* Changed to a GEM package.
|
10
|
+
|
11
|
+
== Version 0.0.2
|
12
|
+
|
13
|
+
* Updated the documentation.
|
14
|
+
* Fixed the install script.
|
15
|
+
|
16
|
+
== Version 0.0.1
|
17
|
+
|
18
|
+
* Initial Version
|
data/README
ADDED
@@ -0,0 +1,214 @@
|
|
1
|
+
= Flex Mock -- Making Mock Easy
|
2
|
+
|
3
|
+
FlexMock is a simple mock object for unit testing. The interface is
|
4
|
+
simple, but still provides a good bit of flexibility.
|
5
|
+
|
6
|
+
= Links
|
7
|
+
|
8
|
+
<b>Documents</b> :: http://onestepback.org/software/flexmock
|
9
|
+
<b>Download</b> :: Use RubyGems to download the flexmock gem from http://gems.rubyforge.org
|
10
|
+
|
11
|
+
== Installation
|
12
|
+
|
13
|
+
You can install FlexMock with the following command.
|
14
|
+
|
15
|
+
$ gem install flexmock
|
16
|
+
|
17
|
+
== Simple Example
|
18
|
+
|
19
|
+
We have a data acquisition class (+TemperatureSampler+) that reads a
|
20
|
+
temperature sensor and returns an average of 3 readings. We don't
|
21
|
+
have a _real_ temperature to use for testing, so we mock one up with a
|
22
|
+
mock object that responds to the +read_temperature+ message.
|
23
|
+
|
24
|
+
Here's the complete example:
|
25
|
+
|
26
|
+
class TemperatureSampler
|
27
|
+
def initialize(sensor)
|
28
|
+
@sensor = sensor
|
29
|
+
end
|
30
|
+
|
31
|
+
def average_temp
|
32
|
+
total = (0...3).collect { @sensor.read_temperature }.inject { |i, s| i + s }
|
33
|
+
total / 3.0
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class TestTemperatureSampler < Test::Unit::TestCase
|
38
|
+
def test_tempurature_sampler
|
39
|
+
readings = [10, 12, 14]
|
40
|
+
FlexMock.use("temp") do |sensor|
|
41
|
+
sensor = FlexMock.new
|
42
|
+
sensor.should_receive(:read_temperature).and_return { readings.shift }
|
43
|
+
sampler = TemperatureSampler.new(sensor)
|
44
|
+
assert_equal 12, sampler.average_temp
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
== Quick Reference
|
49
|
+
|
50
|
+
The following declarators may be used to create the proper
|
51
|
+
expectations on a FlexMock object.
|
52
|
+
|
53
|
+
* <tt>should_receive(<em>symbol</em>)</tt> -- Declares that a message
|
54
|
+
named <em>symbol</em> will be sent to the mock object. Further
|
55
|
+
refinements on this expected message (called an expectation) may be
|
56
|
+
chained to the +should_receive+ call.
|
57
|
+
* <tt>with(<em>arglist</em>)</tt> -- Declares that this expectation
|
58
|
+
matches messages that match the given argument list. The
|
59
|
+
<tt>===</tt> operator is used on a argument by argument basis to
|
60
|
+
determine matching. This means that most literal values match
|
61
|
+
literally, class values match any instance of a class and regular
|
62
|
+
expression match any matching string (after a +to_s+ conversion).
|
63
|
+
* <tt>with_any_args</tt> -- Declares that this expectation matches the
|
64
|
+
message with any argument (default)
|
65
|
+
* <tt>with_no_args</tt> -- Declares that this expectation matches
|
66
|
+
messages with no arguments
|
67
|
+
* <tt>returns(<em>value</em>)</tt> -- Declares that the message will
|
68
|
+
return the given value (<tt>returns(nil)</tt> is the default).
|
69
|
+
* <tt>returns { code ... }</tt> -- Declares that the message will
|
70
|
+
return whatever the block calculates.
|
71
|
+
* <tt>zero_or_more_times</tt> -- Declares that the message is may be
|
72
|
+
sent zero or more times (default, equivalent to
|
73
|
+
<tt>at_least.never</tt>).
|
74
|
+
* <tt>once</tt> -- Declares that the message is only sent once.
|
75
|
+
<tt>at_least</tt> / <tt>at_most</tt> modifiers are allowed.
|
76
|
+
* <tt>twice</tt> -- Declares that the message is only sent twice.
|
77
|
+
<tt>at_least</tt> / <tt>at_most</tt> modifiers are allowed.
|
78
|
+
* <tt>never</tt> -- Declares that the message is never sent.
|
79
|
+
<tt>at_least</tt> / <tt>at_most</tt> modifiers are allowed.
|
80
|
+
* <tt>times(<em>n</em>)</tt> -- Declares that the message is sent
|
81
|
+
<em>n</em> times. <tt>at_least</tt> / <tt>at_most</tt> modifiers
|
82
|
+
are allowed.
|
83
|
+
* <tt>at_least</tt> -- Modifies the immediately following message
|
84
|
+
count declarator so that it means the message is sent at least that
|
85
|
+
number of times. E.g. <tt>at_least.once</tt> means the message is
|
86
|
+
sent at least once during the test, but may be sent more often.
|
87
|
+
Both <tt>at_least</tt> and <tt>at_most</tt> may be specified on the
|
88
|
+
same expectation.
|
89
|
+
* <tt>at_most</tt> -- Similar to <tt>at_least</tt>, but puts an upper
|
90
|
+
limit on the number of messages. Both <tt>at_least</tt> and
|
91
|
+
<tt>at_most</tt> may be specified on the same expectation.
|
92
|
+
* <tt>ordered</tt> -- Declares that the message is ordered and is
|
93
|
+
expected to be received in a certain position in a sequence of
|
94
|
+
messages. The message should arrive after and previously declared
|
95
|
+
ordered messages and prior to any following declared ordered
|
96
|
+
messages. Unordered messages are ignored when considering the
|
97
|
+
message order.
|
98
|
+
* <tt>ordered(<em>n</em>)</tt> -- Declares that the message is ordered
|
99
|
+
with a specific order number. Order numbers are normally supplied
|
100
|
+
sequentially starting with 1. Explicitly ordered messages must have
|
101
|
+
a sequence number greater than the prior implicit order number.
|
102
|
+
Using explicit order allows messages to be grouped so that the order
|
103
|
+
of messages in a group (sharing an order number) can be received in
|
104
|
+
any sequence, but the order between groups is still maintained. See
|
105
|
+
the explicit ordering example below.
|
106
|
+
|
107
|
+
== Examples
|
108
|
+
|
109
|
+
=== Expect multiple queries and a single update
|
110
|
+
|
111
|
+
The queries my have any arguments. The update must have a specific
|
112
|
+
argument of 5.
|
113
|
+
|
114
|
+
FlexMock('db').use |db|
|
115
|
+
db.should_receive(:query).and_return([1,2,3])
|
116
|
+
db.should_recieve(:update).with(5).and_return(nil).once
|
117
|
+
# test code here
|
118
|
+
end
|
119
|
+
|
120
|
+
=== Expect all queries before any updates
|
121
|
+
|
122
|
+
All the query message must occur before any of the update messages.
|
123
|
+
|
124
|
+
FlexMock('db').use |db|
|
125
|
+
db.should_receive(:query).and_return([1,2,3]).ordered
|
126
|
+
db.should_recieve(:update).and_return(nil).ordered
|
127
|
+
# test code here
|
128
|
+
end
|
129
|
+
|
130
|
+
=== Expect several queries with different parameters
|
131
|
+
|
132
|
+
The queries should happen after startup but before finish. The
|
133
|
+
queries themselves may happen in any order (because they have the same
|
134
|
+
order number). The first two queries should happen exactly once, but
|
135
|
+
the third query (which matches any query call with a four character
|
136
|
+
parameter) may be called multiple times (but at least once). Startup
|
137
|
+
and finish must also happen exactly once.
|
138
|
+
|
139
|
+
Also note that we use the +with+ method to match different arguement
|
140
|
+
values to figure out what value to return.
|
141
|
+
|
142
|
+
FlexMock('db').use |db|
|
143
|
+
db.should_receive(:startup).once.ordered
|
144
|
+
db.should_receive(:query).with("CPWR").and_return(12.3).once.ordered(10)
|
145
|
+
db.should_receive(:query).with("MSFT").and_return(10.0).once.ordered(10)
|
146
|
+
db.should_receive(:query).with(/^....$/).and_return(3.3).at_least.once.ordered(10)
|
147
|
+
db.should_receive(:finish).once.ordered
|
148
|
+
# test code here
|
149
|
+
end
|
150
|
+
|
151
|
+
=== Expect multiple calls, returning a different value each time
|
152
|
+
|
153
|
+
Sometimes you need to return different values for each call to a
|
154
|
+
mocked method. This example shifts values out of a list for this
|
155
|
+
effect.
|
156
|
+
|
157
|
+
FlexMock('file').use |file|
|
158
|
+
return_values = ["line 1\n", "line 2\n"]
|
159
|
+
file.should_receive(:gets).with_no_args.and_return { return_values.shift }
|
160
|
+
# test code here
|
161
|
+
end
|
162
|
+
|
163
|
+
=== Ignore uninteresting messages
|
164
|
+
|
165
|
+
Generally you need to mock only those methods that return an
|
166
|
+
interesting value or wish to assert were sent in a particular manner.
|
167
|
+
Use the +should_ignore_missing+ method to turn on missing method
|
168
|
+
ignoring.
|
169
|
+
|
170
|
+
FlexMock('m').use |m|
|
171
|
+
m.should_recieve(:an_important_message).and_return(1).once
|
172
|
+
m.should_ignore_missing
|
173
|
+
# test code here
|
174
|
+
end
|
175
|
+
|
176
|
+
<b>Note:</b> The original +mock_ignore_missing+ is now an alias for
|
177
|
+
+should_ignore_missing+.
|
178
|
+
|
179
|
+
== Classic +mock_handle+ Interface
|
180
|
+
|
181
|
+
FlexMock still supports the simple +mock_handle+ interface used in the
|
182
|
+
original version of FlexMock. +mock_handle+ is equivalent to the
|
183
|
+
following:
|
184
|
+
|
185
|
+
def mock_handle(sym, expected_count=nil, &block)
|
186
|
+
self.should_receive(sym).times(expected_count).returns(&block)
|
187
|
+
end
|
188
|
+
|
189
|
+
== Other Mock Objects
|
190
|
+
|
191
|
+
ruby-mock :: http://www.b13media.com/dev/ruby/mock.html
|
192
|
+
test-unit-mock :: http://www.deveiate.org/code/Test-Unit-Mock.shtml
|
193
|
+
|
194
|
+
== License
|
195
|
+
|
196
|
+
Copyright 2003, 2004, 2005 by Jim Weirich (jim@weirichhouse.org).
|
197
|
+
All rights reserved.
|
198
|
+
|
199
|
+
Permission is granted for use, copying, modification, distribution,
|
200
|
+
and distribution of modified versions of this work as long as the
|
201
|
+
above copyright notice is included.
|
202
|
+
|
203
|
+
|
204
|
+
= Other stuff
|
205
|
+
|
206
|
+
Author:: Jim Weirich <jim@weirichhouse.org>
|
207
|
+
Requires:: Ruby 1.8.x or later
|
208
|
+
|
209
|
+
== Warranty
|
210
|
+
|
211
|
+
This software is provided "as is" and without any express or
|
212
|
+
implied warranties, including, without limitation, the implied
|
213
|
+
warranties of merchantibility and fitness for a particular
|
214
|
+
purpose.
|
data/Rakefile
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
# Rakefile for flexmock -*- ruby -*-
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'rake/gempackagetask'
|
5
|
+
require 'rake/clean'
|
6
|
+
require 'rake/rdoctask'
|
7
|
+
require 'rake/testtask'
|
8
|
+
|
9
|
+
CLOBBER.include("html", 'pkg')
|
10
|
+
|
11
|
+
PKG_VERSION = '0.1.1'
|
12
|
+
|
13
|
+
PKG_FILES = FileList[
|
14
|
+
'[A-Z]*',
|
15
|
+
'lib/**/*.rb',
|
16
|
+
'test/**/*.rb',
|
17
|
+
'*.blurb',
|
18
|
+
'install.rb'
|
19
|
+
]
|
20
|
+
|
21
|
+
RDOC_FILES = FileList[
|
22
|
+
'README',
|
23
|
+
'CHANGELOG',
|
24
|
+
'lib/**/*.rb',
|
25
|
+
'doc/**/*.rdoc',
|
26
|
+
'test/*.rb'
|
27
|
+
]
|
28
|
+
|
29
|
+
|
30
|
+
task :default => [:test]
|
31
|
+
|
32
|
+
# Test Targets -------------------------------------------------------
|
33
|
+
|
34
|
+
Rake::TestTask.new do |t|
|
35
|
+
t.pattern = 'test/test*.rb'
|
36
|
+
t.verbose = true
|
37
|
+
end
|
38
|
+
|
39
|
+
# RDoc Target --------------------------------------------------------
|
40
|
+
|
41
|
+
rd = Rake::RDocTask.new("rdoc") do |rdoc|
|
42
|
+
rdoc.rdoc_dir = 'html'
|
43
|
+
rdoc.template = 'doc/jamis.rb'
|
44
|
+
# rdoc.template = 'html'
|
45
|
+
# rdoc.template = 'kilmer'
|
46
|
+
# rdoc.template = 'css2'
|
47
|
+
rdoc.title = "Flex Mock"
|
48
|
+
rdoc.options << '--line-numbers' << '--inline-source' << '--main' << 'README'
|
49
|
+
rdoc.rdoc_files.include('[A-Z][A-Z]*')
|
50
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
51
|
+
end
|
52
|
+
|
53
|
+
# Package Task -------------------------------------------------------
|
54
|
+
|
55
|
+
if ! defined?(Gem)
|
56
|
+
puts "Package Target requires RubyGEMs"
|
57
|
+
else
|
58
|
+
spec = Gem::Specification.new do |s|
|
59
|
+
|
60
|
+
#### Basic information.
|
61
|
+
|
62
|
+
s.name = 'flexmock'
|
63
|
+
s.version = PKG_VERSION
|
64
|
+
s.summary = "Simple and Flexible Mock Objects for Testing"
|
65
|
+
s.description = %{
|
66
|
+
FlexMock is a extremely simple mock object class compatible
|
67
|
+
with the Test::Unit framework. Although the FlexMock's
|
68
|
+
interface is simple, it is very flexible.
|
69
|
+
}
|
70
|
+
|
71
|
+
#### Dependencies and requirements.
|
72
|
+
|
73
|
+
#s.add_dependency('log4r', '> 1.0.4')
|
74
|
+
#s.requirements << ""
|
75
|
+
|
76
|
+
#### Which files are to be included in this gem? Everything! (Except CVS directories.)
|
77
|
+
|
78
|
+
s.files = PKG_FILES.to_a
|
79
|
+
|
80
|
+
#### C code extensions.
|
81
|
+
|
82
|
+
#s.extensions << "ext/rmagic/extconf.rb"
|
83
|
+
|
84
|
+
#### Load-time details: library and application (you will need one or both).
|
85
|
+
|
86
|
+
s.require_path = 'lib' # Use these for libraries.
|
87
|
+
s.autorequire = 'flexmock'
|
88
|
+
|
89
|
+
#### Documentation and testing.
|
90
|
+
|
91
|
+
s.has_rdoc = true
|
92
|
+
s.extra_rdoc_files = rd.rdoc_files.reject { |fn| fn =~ /\.rb$/ }.to_a
|
93
|
+
s.rdoc_options <<
|
94
|
+
'--title' << 'Flex Mock' <<
|
95
|
+
'--main' << 'README' <<
|
96
|
+
'--line-numbers'
|
97
|
+
|
98
|
+
#### Author and project details.
|
99
|
+
|
100
|
+
s.author = "Jim Weirich"
|
101
|
+
s.email = "jim@weirichhouse.org"
|
102
|
+
s.homepage = "http://http://onestepback.org"
|
103
|
+
end
|
104
|
+
|
105
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
106
|
+
pkg.need_zip = true
|
107
|
+
pkg.need_tar = true
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
require 'rake/contrib/publisher'
|
112
|
+
require 'rake/contrib/sshpublisher'
|
113
|
+
|
114
|
+
task :publish => [:rdoc] do
|
115
|
+
html_publisher = Rake::SshFreshDirPublisher.new(
|
116
|
+
'umlcoop',
|
117
|
+
'htdocs/software/flexmock',
|
118
|
+
'html')
|
119
|
+
blurb_publisher = Rake::SshFilePublisher.new(
|
120
|
+
'umlcoop',
|
121
|
+
'htdocs/software/flexmock',
|
122
|
+
'.',
|
123
|
+
'flexmock.blurb')
|
124
|
+
html_publisher.upload
|
125
|
+
blurb_publisher.upload
|
126
|
+
end
|
127
|
+
|
128
|
+
|
data/flexmock.blurb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
name: flexmock
|
2
|
+
document: http://onestepback.org/software/flexmock
|
3
|
+
download: http://onestepback.org/packages/flexmock
|
4
|
+
description: >
|
5
|
+
<p>FlexMock is the outcome of a frustrating evening of trying to
|
6
|
+
use the original mock object by Nat Pryce. Nat's mock object is
|
7
|
+
really cool, but it assumes that we know the exact calling order
|
8
|
+
of all the methods to the mock object. I really feel that
|
9
|
+
over-constrains the solution code. This little quicky seems to
|
10
|
+
meet my needs fairly well.</p>
|
data/install.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rbconfig'
|
2
|
+
require 'find'
|
3
|
+
require 'ftools'
|
4
|
+
|
5
|
+
include Config
|
6
|
+
|
7
|
+
def indir(newdir)
|
8
|
+
olddir = Dir.pwd
|
9
|
+
Dir.chdir(newdir)
|
10
|
+
yield
|
11
|
+
ensure
|
12
|
+
Dir.chdir(olddir)
|
13
|
+
end
|
14
|
+
|
15
|
+
$ruby = CONFIG['ruby_install_name']
|
16
|
+
$sitedir = CONFIG["sitelibdir"]
|
17
|
+
|
18
|
+
unless $sitedir
|
19
|
+
version = CONFIG["MAJOR"]+"."+CONFIG["MINOR"]
|
20
|
+
$libdir = File.join(CONFIG["libdir"], "ruby", version)
|
21
|
+
$sitedir = $:.find {|x| x =~ /site_ruby/}
|
22
|
+
if !$sitedir
|
23
|
+
$sitedir = File.join($libdir, "site_ruby")
|
24
|
+
elsif $sitedir !~ Regexp.quote(version)
|
25
|
+
$sitedir = File.join($sitedir, version)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# The library files
|
30
|
+
|
31
|
+
file = 'flexmock.rb'
|
32
|
+
|
33
|
+
indir('lib') do
|
34
|
+
Dir['**/*.rb'].each do |file|
|
35
|
+
File::install(
|
36
|
+
file,
|
37
|
+
File.join($sitedir, file),
|
38
|
+
0644,
|
39
|
+
true)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
data/lib/flexmock.rb
ADDED
@@ -0,0 +1,485 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#---
|
4
|
+
# Copyright 2003, 2004, 2005 by Jim Weirich (jim@weirichhouse.org).
|
5
|
+
# All rights reserved.
|
6
|
+
|
7
|
+
# Permission is granted for use, copying, modification, distribution,
|
8
|
+
# and distribution of modified versions of this work as long as the
|
9
|
+
# above copyright notice is included.
|
10
|
+
#+++
|
11
|
+
|
12
|
+
require 'test/unit'
|
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
|
+
# returns("STUFF")
|
30
|
+
# m.should_receive(:downcase).with(String).
|
31
|
+
# returns { |s| s.downcase }.once
|
32
|
+
#
|
33
|
+
class FlexMock
|
34
|
+
include Test::Unit::Assertions
|
35
|
+
|
36
|
+
attr_reader :mock_name
|
37
|
+
attr_accessor :mock_current_order
|
38
|
+
|
39
|
+
# Create a FlexMock object with the given name. The name is used in
|
40
|
+
# error messages.
|
41
|
+
def initialize(name="unknown")
|
42
|
+
@mock_name = name
|
43
|
+
@expectations = Hash.new
|
44
|
+
@allocated_order = 0
|
45
|
+
@mock_current_order = 0
|
46
|
+
end
|
47
|
+
|
48
|
+
# Handle all messages denoted by +sym+ by calling the given block
|
49
|
+
# and passing any parameters to the block. If we know exactly how
|
50
|
+
# many calls are to be made to a particular method, we may check
|
51
|
+
# that by passing in the number of expected calls as a second
|
52
|
+
# paramter.
|
53
|
+
def mock_handle(sym, expected_count=nil, &block)
|
54
|
+
self.should_receive(sym).times(expected_count).returns(&block)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Verify that each method that had an explicit expected count was
|
58
|
+
# actually called that many times.
|
59
|
+
def mock_verify
|
60
|
+
mock_wrap do
|
61
|
+
@expectations.each do |sym, handler|
|
62
|
+
handler.mock_verify
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Allocation a new order number from the mock.
|
68
|
+
def mock_allocate_order
|
69
|
+
@auto_allocate = true
|
70
|
+
@allocated_order += 1
|
71
|
+
end
|
72
|
+
|
73
|
+
# Allocation a new order number from the mock.
|
74
|
+
def mock_reallocate(n)
|
75
|
+
if @auto_allocate && n <= @allocated_order
|
76
|
+
FlexMock.failure "explicit order number #{n} must be greater than " +
|
77
|
+
@allocated_order.to_s
|
78
|
+
elsif (n < @allocated_order)
|
79
|
+
FlexMock.failure "explicit order number #{n} must be greater than or equal to " +
|
80
|
+
@allocated_order.to_s
|
81
|
+
end
|
82
|
+
@auto_allocate = false
|
83
|
+
@allocated_order = n
|
84
|
+
end
|
85
|
+
|
86
|
+
# Ignore all undefined (missing) method calls.
|
87
|
+
def should_ignore_missing
|
88
|
+
@ignore_missing = true
|
89
|
+
end
|
90
|
+
alias mock_ignore_missing should_ignore_missing
|
91
|
+
|
92
|
+
# Handle missing methods by attempting to look up a handler.
|
93
|
+
def method_missing(sym, *args, &block)
|
94
|
+
mock_wrap do
|
95
|
+
if handler = @expectations[sym]
|
96
|
+
args << block if block_given?
|
97
|
+
handler.call(*args)
|
98
|
+
else
|
99
|
+
super(sym, *args, &block) unless @ignore_missing
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Override the built-in respond_to? to include the mocked methods.
|
105
|
+
def respond_to?(sym)
|
106
|
+
super || (@expectations[sym] ? true : @ignore_missing)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Override the built-in +method+ to include the mocked methods.
|
110
|
+
def method(sym)
|
111
|
+
@expectations[sym] || super
|
112
|
+
rescue NameError => ex
|
113
|
+
if @ignore_missing
|
114
|
+
proc { }
|
115
|
+
else
|
116
|
+
raise ex
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Declare that the mock object should receive a message with the
|
121
|
+
# given name. An expectation object for the method name is returned
|
122
|
+
# as the result of this method. Further expectation constraints can
|
123
|
+
# be added by chaining to the result.
|
124
|
+
#
|
125
|
+
# See Expectation for a list of declarators that can be used.
|
126
|
+
def should_receive(sym)
|
127
|
+
@expectations[sym] ||= ExpectationDirector.new(sym)
|
128
|
+
result = Expectation.new(self, sym)
|
129
|
+
@expectations[sym] << result
|
130
|
+
result
|
131
|
+
end
|
132
|
+
|
133
|
+
# Class method to make sure that verify is called at the end of a
|
134
|
+
# test. One mock object will be created for each name given to the
|
135
|
+
# use method. The mocks will be passed to the block as arguments.
|
136
|
+
# If no names are given, then a single anonymous mock object will be
|
137
|
+
# created.
|
138
|
+
#
|
139
|
+
# At the end of the use block, each mock object will be verified to
|
140
|
+
# make sure the proper number of calls have been made.
|
141
|
+
#
|
142
|
+
# Usage:
|
143
|
+
#
|
144
|
+
# FlexMock.use("name") do |mock| # Creates a mock named "name"
|
145
|
+
# mock.should_receive(:meth).
|
146
|
+
# returns(0).once
|
147
|
+
# end # mock is verified here
|
148
|
+
#
|
149
|
+
def self.use(*names)
|
150
|
+
names = ["unknown"] if names.empty?
|
151
|
+
got_excecption = false
|
152
|
+
mocks = names.collect { |n| new(n) }
|
153
|
+
yield(*mocks)
|
154
|
+
rescue Exception => ex
|
155
|
+
got_exception = true
|
156
|
+
raise
|
157
|
+
ensure
|
158
|
+
mocks.each do |mock|
|
159
|
+
mock.mock_verify unless got_exception
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
# Class method to format a method name and argument list as a nice
|
165
|
+
# looking string.
|
166
|
+
def self.format_args(sym, args)
|
167
|
+
if args
|
168
|
+
"#{sym}(#{args.collect { |a| a.inspect }.join(', ')})"
|
169
|
+
else
|
170
|
+
"#{sym}(*args)"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# Class method to consistently form failure exceptions
|
175
|
+
def self.failure(msg)
|
176
|
+
fail Test::Unit::AssertionFailedError, msg
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
|
181
|
+
# Wrap a block of code so the any assertion errors are wrapped so
|
182
|
+
# that the mock name is added to the error message .
|
183
|
+
def mock_wrap(&block)
|
184
|
+
yield
|
185
|
+
rescue Test::Unit::AssertionFailedError => ex
|
186
|
+
raise Test::Unit::AssertionFailedError,
|
187
|
+
"in mock '#{@mock_name}': #{ex.message}",
|
188
|
+
ex.backtrace
|
189
|
+
end
|
190
|
+
|
191
|
+
####################################################################
|
192
|
+
# The expectation director is responsible for routing calls to the
|
193
|
+
# correct expectations for a given argument list.
|
194
|
+
#
|
195
|
+
class ExpectationDirector
|
196
|
+
|
197
|
+
# Create an ExpectationDirector for a mock object.
|
198
|
+
def initialize(sym)
|
199
|
+
@sym = sym
|
200
|
+
@expectations = []
|
201
|
+
@expected_order = nil
|
202
|
+
end
|
203
|
+
|
204
|
+
# Invoke the expectations for a given set of arguments.
|
205
|
+
def call(*args)
|
206
|
+
exp = @expectations.find { |e| e.match_args(args) } ||
|
207
|
+
@expectations.find { |e| e.expected_args.nil? }
|
208
|
+
if exp.nil?
|
209
|
+
FlexMock.failure "no matching handler found for " +
|
210
|
+
FlexMock.format_args(@sym, args)
|
211
|
+
end
|
212
|
+
exp.verify_call(*args)
|
213
|
+
end
|
214
|
+
|
215
|
+
# Same as call.
|
216
|
+
def [](*args)
|
217
|
+
call(*args)
|
218
|
+
end
|
219
|
+
|
220
|
+
# Append an expectation to this director.
|
221
|
+
def <<(expectation)
|
222
|
+
@expectations << expectation
|
223
|
+
end
|
224
|
+
|
225
|
+
# Do the post test verification for this directory. Check all the
|
226
|
+
# expectations.
|
227
|
+
def mock_verify
|
228
|
+
@expectations.each do |exp|
|
229
|
+
exp.mock_verify
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
####################################################################
|
235
|
+
# Base class for all the count validators.
|
236
|
+
#
|
237
|
+
class CountValidator
|
238
|
+
include Test::Unit::Assertions
|
239
|
+
def initialize(sym, limit)
|
240
|
+
@sym = sym
|
241
|
+
@limit = limit
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
####################################################################
|
246
|
+
# Validator for exact call counts.
|
247
|
+
#
|
248
|
+
class ExactCountValidator < CountValidator
|
249
|
+
# Validate that the method expectation was called exactly +n+
|
250
|
+
# times.
|
251
|
+
def validate(n)
|
252
|
+
assert_equal @limit, n,
|
253
|
+
"method '#{@sym}' called incorrect number of times"
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
####################################################################
|
258
|
+
# Validator for call counts greater than or equal to a limit.
|
259
|
+
#
|
260
|
+
class AtLeastCountValidator < CountValidator
|
261
|
+
# Validate the method expectation was called no more than +n+
|
262
|
+
# times.
|
263
|
+
def validate(n)
|
264
|
+
assert n >= @limit,
|
265
|
+
"Method '#{@sym}' should be called at least #{@limit} times,\n" +
|
266
|
+
"only called #{n} times"
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
####################################################################
|
271
|
+
# Validator for call counts less than or equal to a limit.
|
272
|
+
#
|
273
|
+
class AtMostCountValidator < CountValidator
|
274
|
+
# Validate the method expectation was called at least +n+ times.
|
275
|
+
def validate(n)
|
276
|
+
assert n <= @limit,
|
277
|
+
"Method '#{@sym}' should be called at most #{@limit} times,\n" +
|
278
|
+
"only called #{n} times"
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
####################################################################
|
283
|
+
# An Expectation is returned from each +should_receive+ message sent
|
284
|
+
# to mock object. Each expectation records how a message matching
|
285
|
+
# the message name (argument to +should_receive+) and the argument
|
286
|
+
# list (given by +with+) should behave. Mock expectations can be
|
287
|
+
# recorded by chaining the declaration methods defined in this
|
288
|
+
# class.
|
289
|
+
#
|
290
|
+
# For example:
|
291
|
+
#
|
292
|
+
# mock.should_receive(:meth).with(args).and_returns(result)
|
293
|
+
#
|
294
|
+
class Expectation
|
295
|
+
include Test::Unit::Assertions
|
296
|
+
|
297
|
+
attr_reader :expected_args, :mock, :order_number
|
298
|
+
|
299
|
+
# Create an expectation for a method named +sym+.
|
300
|
+
def initialize(mock, sym)
|
301
|
+
@mock = mock
|
302
|
+
@sym = sym
|
303
|
+
@expected_args = nil
|
304
|
+
@count_validators = []
|
305
|
+
@count_validator_class = ExactCountValidator
|
306
|
+
@actual_count = 0
|
307
|
+
@return_value = nil
|
308
|
+
@return_block = lambda { @return_value }
|
309
|
+
@order_number = nil
|
310
|
+
end
|
311
|
+
|
312
|
+
def to_s
|
313
|
+
FlexMock.format_args(@sym, @expected_args)
|
314
|
+
end
|
315
|
+
|
316
|
+
# Verify the current call with the given arguments matches the
|
317
|
+
# expectations recorded in this object.
|
318
|
+
def verify_call(*args)
|
319
|
+
validate_order
|
320
|
+
@actual_count += 1
|
321
|
+
@return_block.call(*args)
|
322
|
+
end
|
323
|
+
|
324
|
+
# Validate that the order
|
325
|
+
def validate_order
|
326
|
+
return if @order_number.nil?
|
327
|
+
if @order_number < @mock.mock_current_order
|
328
|
+
FlexMock.failure "method #{to_s} called out of order " +
|
329
|
+
"(expected order #{@order_number}, was #{@mock.mock_current_order})"
|
330
|
+
end
|
331
|
+
@mock.mock_current_order = @order_number
|
332
|
+
end
|
333
|
+
private :validate_order
|
334
|
+
|
335
|
+
# Validate the correct number of calls have been made. Called by
|
336
|
+
# the teardown process.
|
337
|
+
def mock_verify
|
338
|
+
@count_validators.each do |v|
|
339
|
+
v.validate(@actual_count)
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
# Does the argument list match this expectation's argument
|
344
|
+
# specification.
|
345
|
+
def match_args(args)
|
346
|
+
return false if @expected_args.nil?
|
347
|
+
return false if args.size != @expected_args.size
|
348
|
+
(0...args.size).all? { |i| match_arg(@expected_args[i], args[i]) }
|
349
|
+
end
|
350
|
+
|
351
|
+
# Does the expected argument match the corresponding actual value.
|
352
|
+
def match_arg(expected, actual)
|
353
|
+
expected === actual ||
|
354
|
+
expected === actual.to_s ||
|
355
|
+
expected == actual
|
356
|
+
end
|
357
|
+
|
358
|
+
# Declare that the method should expect the given argument list.
|
359
|
+
def with(*args)
|
360
|
+
@expected_args = args
|
361
|
+
self
|
362
|
+
end
|
363
|
+
|
364
|
+
# Declare that the method should be called with no arguments.
|
365
|
+
def with_no_args
|
366
|
+
with
|
367
|
+
end
|
368
|
+
|
369
|
+
# Declare that the method can be called with any number of
|
370
|
+
# arguments of any type.
|
371
|
+
def with_any_args
|
372
|
+
@expected_args = nil
|
373
|
+
self
|
374
|
+
end
|
375
|
+
|
376
|
+
# Declare that the method returns a particular value (when the
|
377
|
+
# argument list is matched). If a block is given, it is evaluated
|
378
|
+
# on each call and its value is returned. +and_return+ is an
|
379
|
+
# alias for +returns+.
|
380
|
+
#
|
381
|
+
# For example:
|
382
|
+
#
|
383
|
+
# mock.should_receive(:f).returns(12) # returns 12
|
384
|
+
#
|
385
|
+
# mock.should_receive(:f).with(String). # returns an
|
386
|
+
# returns { |str| str.upcase } # upcased string
|
387
|
+
#
|
388
|
+
def returns(value=nil, &block)
|
389
|
+
@return_block = block_given? ? block : lambda { value }
|
390
|
+
self
|
391
|
+
end
|
392
|
+
alias :and_return :returns # :nodoc:
|
393
|
+
|
394
|
+
# Declare that the method may be called any number of times.
|
395
|
+
def zero_or_more_times
|
396
|
+
at_least.never
|
397
|
+
end
|
398
|
+
|
399
|
+
# Declare that the method is called +limit+ times with the
|
400
|
+
# declared argument list. This may be modified by the +at_least+
|
401
|
+
# and +at_most+ declarators.
|
402
|
+
def times(limit)
|
403
|
+
@count_validators << @count_validator_class.new(@sym, limit) unless limit.nil?
|
404
|
+
@count_validator_class = ExactCountValidator
|
405
|
+
self
|
406
|
+
end
|
407
|
+
|
408
|
+
# Declare that the method is never expected to be called with the
|
409
|
+
# given argument list. This may be modified by the +at_least+ and
|
410
|
+
# +at_most+ declarators.
|
411
|
+
def never
|
412
|
+
times(0)
|
413
|
+
end
|
414
|
+
|
415
|
+
# Declare that the method is expected to be called exactly once
|
416
|
+
# with the given argument list. This may be modified by the
|
417
|
+
# +at_least+ and +at_most+ declarators.
|
418
|
+
def once
|
419
|
+
times(1)
|
420
|
+
end
|
421
|
+
|
422
|
+
# Declare that the method is expected to be called exactly twice
|
423
|
+
# with the given argument list. This may be modified by the
|
424
|
+
# +at_least+ and +at_most+ declarators.
|
425
|
+
def twice
|
426
|
+
times(2)
|
427
|
+
end
|
428
|
+
|
429
|
+
# Modifies the next call count declarator (+times+, +never+,
|
430
|
+
# +once+ or +twice+) so that the declarator means the method is
|
431
|
+
# called at least that many times.
|
432
|
+
#
|
433
|
+
# E.g. method f must be called at least twice:
|
434
|
+
#
|
435
|
+
# mock.should_receive(:f).at_least.twice
|
436
|
+
#
|
437
|
+
def at_least
|
438
|
+
@count_validator_class = AtLeastCountValidator
|
439
|
+
self
|
440
|
+
end
|
441
|
+
|
442
|
+
# Modifies the next call count declarator (+times+, +never+,
|
443
|
+
# +once+ or +twice+) so that the declarator means the method is
|
444
|
+
# called at most that many times.
|
445
|
+
#
|
446
|
+
# E.g. method f must be called no more than twice
|
447
|
+
#
|
448
|
+
# mock.should_receive(:f).at_most.twice
|
449
|
+
#
|
450
|
+
def at_most
|
451
|
+
@count_validator_class = AtMostCountValidator
|
452
|
+
self
|
453
|
+
end
|
454
|
+
|
455
|
+
# Declare that the given method must be called in order. All
|
456
|
+
# ordered method calls must be received in the order specified by
|
457
|
+
# the ordering of the +should_receive+ messages. Receiving a
|
458
|
+
# methods out of the specified order will cause a test failure.
|
459
|
+
#
|
460
|
+
# If the user needs more fine control over ordering
|
461
|
+
# (e.g. specifying that a group of messages may be received in any
|
462
|
+
# order as long as they all come after another group of messages),
|
463
|
+
# a _order_ _number_ may be specified in the +ordered+ calls
|
464
|
+
#
|
465
|
+
# For example, in the following, messages +flip+ and +flop+ may be
|
466
|
+
# received in any order (because they have the same order number),
|
467
|
+
# but must occur strictly after +start+ but before +end+.
|
468
|
+
#
|
469
|
+
# m = FlexMock.new
|
470
|
+
# m.should_receive(:start).ordered
|
471
|
+
# m.should_receive(:flip).ordered(10)
|
472
|
+
# m.should_receive(:flop).ordered(10)
|
473
|
+
# m.should_receive(:end).ordered
|
474
|
+
#
|
475
|
+
def ordered(order=nil)
|
476
|
+
if order.nil?
|
477
|
+
@order_number = @mock.mock_allocate_order
|
478
|
+
else
|
479
|
+
@order_number = order
|
480
|
+
@mock.mock_reallocate order
|
481
|
+
end
|
482
|
+
self
|
483
|
+
end
|
484
|
+
end
|
485
|
+
end
|