flexmock 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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