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 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