flexmock 0.1.1
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 +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
|