hardmock 1.2.0 → 1.2.3

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES CHANGED
@@ -1,4 +1,29 @@
1
- Hardmock 1.0.3
1
+ Hardmock 1.2.3
2
+
3
+ Sat Apr 28 01:16:15 EDT 2007
4
+
5
+ * Re-release of 1.2.2 (which was canceled)... tasks moved to lib/tasks
6
+
7
+ Hardmock 1.2.2
8
+
9
+ Sat Apr 28 00:41:30 EDT 2007
10
+
11
+ * assert_error has been broken out into its own lib file
12
+ * Gem package can now run all tests successfully
13
+ * Internal code refactoring; a number of classes that were defined in hardmock.rb are now in their own files
14
+
15
+ Hardmock 1.2.1
16
+
17
+ Sat Apr 28 00:41:30 EDT 2007
18
+
19
+ * (botched release, see 1.2.2)
20
+
21
+ Hardmock 1.2.0
22
+
23
+ * You can now use "expect" in place of "expects" if you must.
24
+ * "inspect" has been added to the list of methods NOT erased by MethodCleanout.
25
+
26
+ Hardmock 1.1.0
2
27
 
3
28
  * "expects" replaces "expect" ("expect" now raises Hardmock::DeprecationError)
4
29
  * "verify_mocks" is now implicit in teardown, you needn't call it anymore
data/README CHANGED
@@ -7,7 +7,7 @@ Strict, ordered mock objects using very lightweight syntax in your tests.
7
7
 
8
8
  The basic procedure for using Hardmock in your tests is:
9
9
 
10
- * require 'hardmock'
10
+ * require 'hardmock' (this happens automatically when being used as a Rails plugin)
11
11
  * Create some mocks
12
12
  * Setup some expectations
13
13
  * Execute the target code
data/Rakefile CHANGED
@@ -1,94 +1,8 @@
1
1
  require 'rake'
2
- require 'rake/testtask'
3
- require 'rake/rdoctask'
4
2
  require 'rubygems'
5
- require 'rake/gempackagetask'
6
- require 'rake/contrib/sshpublisher'
7
- require 'hoe'
8
3
 
9
- HARDMOCK_VERSION = "1.2.0"
4
+ HARDMOCK_VERSION = "1.2.3"
10
5
 
11
- task :default => [ :alltests ]
6
+ Dir["lib/tasks/*.rake"].each { |f| load f }
12
7
 
13
- desc "Run all the tests"
14
- Rake::TestTask.new("alltests") { |t|
15
- t.libs << "test"
16
- t.pattern = 'test/**/*_test.rb'
17
- t.verbose = true
18
- }
19
-
20
- def add_rdoc_options(options)
21
- options << '--line-numbers' << '--inline-source' << '--main' << 'README' << '--title' << 'Hardmock'
22
- end
23
-
24
- desc "Generate RDoc documentation"
25
- Rake::RDocTask.new { |rdoc|
26
- rdoc.rdoc_dir = 'doc'
27
- rdoc.title = "Hardmock: Strict expectation-based mock object library "
28
- # rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object' << '--main'
29
- add_rdoc_options(rdoc.options)
30
- rdoc.rdoc_files.include('lib/**/*.rb', 'README','CHANGES','LICENSE')
31
- }
32
-
33
- task :showdoc => [ :rerdoc ] do
34
- sh "open doc/index.html"
35
- end
36
-
37
- desc "Generate and upload api docs to rubyforge"
38
- task :upload_doc => :rerdoc do
39
- sh "scp -r doc/* rubyforge.org:/var/www/gforge-projects/hardmock/doc"
40
- sh "scp -r homepage/* rubyforge.org:/var/www/gforge-projects/hardmock/"
41
- end
42
-
43
-
44
-
45
- gem_spec = Gem::Specification.new do | s |
46
- s.name = "hardmock"
47
- s.version = HARDMOCK_VERSION
48
- s.author = "David Crosby"
49
- s.email = "crosby@atomicobject.com"
50
- s.platform = Gem::Platform::RUBY
51
- s.summary = "A strict, ordered, expectation-oriented mock object library."
52
- s.rubyforge_project = 'hardmock'
53
- s.homepage = "http://hardmock.rubyforge.org"
54
- s.autorequire = 'hardmock'
55
-
56
- s.files = FileList['{lib,test}/**/*.rb', '[A-Z]*'].exclude('TODO').to_a
57
-
58
- s.require_path = "lib"
59
- s.test_files = Dir.glob("test/**/*test.rb")
60
-
61
- s.has_rdoc = true
62
- s.extra_rdoc_files = ["README","CHANGES","LICENSE"]
63
- add_rdoc_options(s.rdoc_options)
64
- end
65
-
66
- Rake::GemPackageTask.new(gem_spec) do |pkg|
67
- pkg.need_zip = true
68
- pkg.need_tar = true
69
- end
70
-
71
- task :verify_svn_clean do
72
- # Get clean
73
- sh 'svn up'
74
- status = `svn status`
75
- raise "Please get checked-in and cleaned up before releasing.\n#{status}" unless status == ""
76
- end
77
-
78
- desc "Create a release tar.gz file."
79
- task :release => [:verify_svn_clean, :alltests, :upload_doc, :repackage] do
80
- require 'fileutils'
81
- include FileUtils::Verbose
82
- proj_root = File.expand_path(File.dirname(__FILE__))
83
- begin
84
- cd proj_root
85
-
86
-
87
- # Tag the release by number, then re-tag for stable release (makes nicey nicey for Rails plugin installation)
88
- sh "svn cp . svn+ssh://dcrosby42@rubyforge.org/var/svn/hardmock/tags/rel-#{HARDMOCK_VERSION} -m 'Releasing version #{HARDMOCK_VERSION}'"
89
- sh "svn del svn+ssh://dcrosby42@rubyforge.org/var/svn/hardmock/tags/hardmock -m 'Preparing to update stable release tag'"
90
- sh "svn cp . svn+ssh://dcrosby42@rubyforge.org/var/svn/hardmock/tags/hardmock -m 'Updating stable tag to version #{HARDMOCK_VERSION}'"
91
-
92
- puts "UPLOAD #{Dir['pkg/*.*']} TO RUBYFORGE RELEASE ZONE"
93
- end
94
- end
8
+ task :default => [ 'test:all' ]
@@ -0,0 +1,12 @@
1
+ # The path to the root directory of your application.
2
+ APP_ROOT = File.join(File.dirname(__FILE__), '..')
3
+
4
+ ADDITIONAL_LOAD_PATHS = []
5
+ ADDITIONAL_LOAD_PATHS.concat %w(
6
+ lib
7
+ ).map { |dir| "#{APP_ROOT}/#{dir}" }.select { |dir| File.directory?(dir) }
8
+
9
+ # Prepend to $LOAD_PATH
10
+ ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) }
11
+
12
+ # Require any additional libraries needed
@@ -0,0 +1,21 @@
1
+ require 'test/unit/assertions'
2
+
3
+ module Test::Unit::Assertions
4
+ # A better 'assert_raise'. +patterns+ can be one or more Regexps, or a literal String that
5
+ # must match the entire error message.
6
+ def assert_error(err_type,*patterns,&block)
7
+ assert_not_nil block, "assert_error requires a block"
8
+ assert((err_type and err_type.kind_of?(Class)), "First argument to assert_error has to be an error type")
9
+ err = assert_raise(err_type) do
10
+ block.call
11
+ end
12
+ patterns.each do |pattern|
13
+ case pattern
14
+ when Regexp
15
+ assert_match(pattern, err.message)
16
+ else
17
+ assert_equal pattern, err.message
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,16 @@
1
+ module Hardmock
2
+ # Raised when:
3
+ # * Unexpected method is called on a mock object
4
+ # * Bad arguments passed to an expected call
5
+ class ExpectationError < StandardError; end
6
+
7
+ # Raised for methods that should no longer be called. Hopefully, the exception message contains helpful alternatives.
8
+ class DeprecationError < StandardError; end
9
+
10
+ # Raised when it is discovered that an expected method call was never made.
11
+ class VerifyError < StandardError
12
+ def initialize(msg,unmet_expectations)
13
+ super("#{msg}:" + unmet_expectations.map { |ex| "\n * #{ex.to_s}" }.join)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,220 @@
1
+ require 'hardmock/utils'
2
+
3
+ module Hardmock
4
+ class Expectation
5
+ include Utils
6
+ attr_reader :block_value
7
+
8
+ def initialize(options) #:nodoc:
9
+ @options = options
10
+ end
11
+
12
+ def apply_method_call(mock,mname,args,block) #:nodoc:
13
+ unless @options[:mock].equal?(mock)
14
+ raise anger("Wrong object", mock,mname,args)
15
+ end
16
+ unless @options[:method] == mname
17
+ raise anger("Wrong method",mock,mname,args)
18
+ end
19
+
20
+ # Tester-defined block to invoke at method-call-time:
21
+ expectation_block = @options[:block]
22
+
23
+ expected_args = @options[:arguments]
24
+ # if we have a block, we can skip the argument check if none were specified
25
+ unless (expected_args.nil? || expected_args.empty?) && expectation_block && !@options[:suppress_arguments_to_block]
26
+ unless expected_args == args
27
+ raise anger("Wrong arguments",mock,mname,args)
28
+ end
29
+ end
30
+
31
+ relayed_args = args.dup
32
+ if block
33
+ if expectation_block.nil?
34
+ # Can't handle a runtime block without an expectation block
35
+ raise ExpectationError.new("Unexpected block provided to #{to_s}")
36
+ else
37
+ # Runtime blocks are passed as final argument to the expectation block
38
+ unless @options[:suppress_arguments_to_block]
39
+ relayed_args << block
40
+ else
41
+ # Arguments suppressed; send only the block
42
+ relayed_args = [block]
43
+ end
44
+ end
45
+ end
46
+
47
+ # Run the expectation block:
48
+ @block_value = expectation_block.call(*relayed_args) if expectation_block
49
+
50
+ raise @options[:raises] unless @options[:raises].nil?
51
+
52
+ return_value = @options[:returns]
53
+ if return_value.nil?
54
+ return @block_value
55
+ else
56
+ return return_value
57
+ end
58
+ end
59
+
60
+ # Set the return value for an expected method call.
61
+ # Eg,
62
+ # @cash_machine.expects.withdraw(20,:dollars).returns(20.00)
63
+ def returns(val)
64
+ @options[:returns] = val
65
+ self
66
+ end
67
+
68
+ # Rig an expected method to raise an exception when the mock is invoked.
69
+ #
70
+ # Eg,
71
+ # @cash_machine.expects.withdraw(20,:dollars).raises "Insufficient funds"
72
+ #
73
+ # The argument can be:
74
+ # * an Exception -- will be used directly
75
+ # * a String -- will be used as the message for a RuntimeError
76
+ # * nothing -- RuntimeError.new("An Error") will be raised
77
+ def raises(err=nil)
78
+ case err
79
+ when Exception
80
+ @options[:raises] = err
81
+ when String
82
+ @options[:raises] = RuntimeError.new(err)
83
+ else
84
+ @options[:raises] = RuntimeError.new("An Error")
85
+ end
86
+ self
87
+ end
88
+
89
+ # Convenience method: assumes +block_value+ is set, and is set to a Proc
90
+ # (or anything that responds to 'call')
91
+ #
92
+ # light_event = @traffic_light.trap.subscribe(:light_changes)
93
+ #
94
+ # # This code will meet the expectation:
95
+ # @traffic_light.subscribe :light_changes do |color|
96
+ # puts color
97
+ # end
98
+ #
99
+ # The color-handling block is now stored in <tt>light_event.block_value</tt>
100
+ #
101
+ # The block can be invoked like this:
102
+ #
103
+ # light_event.trigger :red
104
+ #
105
+ # See Mock#trap and Mock#expects for information on using expectation objects
106
+ # after they are set.
107
+ #
108
+ def trigger(*block_arguments)
109
+ unless block_value
110
+ raise ExpectationError.new("No block value is currently set for expectation #{to_s}")
111
+ end
112
+ unless block_value.respond_to?(:call)
113
+ raise ExpectationError.new("Can't apply trigger to #{block_value} for expectation #{to_s}")
114
+ end
115
+ block_value.call *block_arguments
116
+ end
117
+
118
+ # Used when an expected method accepts a block at runtime.
119
+ # When the expected method is invoked, the block passed to
120
+ # that method will be invoked as well.
121
+ #
122
+ # NOTE: ExpectationError will be thrown upon running the expected method
123
+ # if the arguments you set up in +yields+ do not properly match up with
124
+ # the actual block that ends up getting passed.
125
+ #
126
+ # == Examples
127
+ # <b>Single invocation</b>: The block passed to +lock_down+ gets invoked
128
+ # once with no arguments:
129
+ #
130
+ # @safe_zone.expects.lock_down.yields
131
+ #
132
+ # # (works on code that looks like:)
133
+ # @safe_zone.lock_down do
134
+ # # ... this block invoked once
135
+ # end
136
+ #
137
+ # <b>Multi-parameter blocks:</b> The block passed to +each_item+ gets
138
+ # invoked twice, with <tt>:item1</tt> the first time, and with
139
+ # <tt>:item2</tt> the second time:
140
+ #
141
+ # @fruit_basket.expects.each_with_index.yields [:apple,1], [:orange,2]
142
+ #
143
+ # # (works on code that looks like:)
144
+ # @fruit_basket.each_with_index do |fruit,index|
145
+ # # ... this block invoked with fruit=:apple, index=1,
146
+ # # ... and then with fruit=:orange, index=2
147
+ # end
148
+ #
149
+ # <b>Arrays can be passed as arguments too</b>... if the block
150
+ # takes a single argument and you want to pass a series of arrays into it,
151
+ # that will work as well:
152
+ #
153
+ # @list_provider.expects.each_list.yields [1,2,3], [4,5,6]
154
+ #
155
+ # # (works on code that looks like:)
156
+ # @list_provider.each_list do |list|
157
+ # # ... list is [1,2,3] the first time
158
+ # # ... list is [4,5,6] the second time
159
+ # end
160
+ #
161
+ # <b>Return value</b>: You can set the return value for the method that
162
+ # accepts the block like so:
163
+ #
164
+ # @cruncher.expects.do_things.yields(:bean1,:bean2).returns("The Results")
165
+ #
166
+ # <b>Raising errors</b>: You can set the raised exception for the method that
167
+ # accepts the block. NOTE: the error will be raised _after_ the block has
168
+ # been invoked.
169
+ #
170
+ # # :bean1 and :bean2 will be passed to the block, then an error is raised:
171
+ # @cruncher.expects.do_things.yields(:bean1,:bean2).raises("Too crunchy")
172
+ #
173
+ def yields(*items)
174
+ @options[:suppress_arguments_to_block] = true
175
+ if items.empty?
176
+ # Yield once
177
+ @options[:block] = lambda do |block|
178
+ if block.arity != 0 and block.arity != -1
179
+ raise ExpectationError.new("Can't pass #{item.inspect} to block with arity #{block.arity} to <#{to_s}>")
180
+ end
181
+ block.call
182
+ end
183
+ else
184
+ # Yield one or more specific items
185
+ @options[:block] = lambda do |block|
186
+ items.each do |item|
187
+ if item.kind_of?(Array)
188
+ if block.arity == item.size
189
+ # Unfold the array into the block's arguments:
190
+ block.call *item
191
+ elsif block.arity == 1
192
+ # Just pass the array in
193
+ block.call item
194
+ else
195
+ # Size mismatch
196
+ raise ExpectationError.new("Can't pass #{item.inspect} to block with arity #{block.arity} to <#{to_s}>")
197
+ end
198
+ else
199
+ if block.arity != 1
200
+ # Size mismatch
201
+ raise ExpectationError.new("Can't pass #{item.inspect} to block with arity #{block.arity} to <#{to_s}>")
202
+ end
203
+ block.call item
204
+ end
205
+ end
206
+ end
207
+ end
208
+ self
209
+ end
210
+
211
+ def to_s # :nodoc:
212
+ format_method_call_string(@options[:mock],@options[:method],@options[:arguments])
213
+ end
214
+
215
+ private
216
+ def anger(msg, mock,mname,args)
217
+ ExpectationError.new("#{msg}: expected call <#{to_s}> but was <#{format_method_call_string(mock,mname,args)}>")
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,9 @@
1
+ require 'hardmock/expectation'
2
+
3
+ module Hardmock
4
+ class ExpectationBuilder #:nodoc:
5
+ def build_expectation(options)
6
+ Expectation.new(options)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,26 @@
1
+ require 'hardmock/method_cleanout'
2
+ require 'hardmock/errors'
3
+
4
+ module Hardmock
5
+ class Expector #:nodoc:
6
+ include MethodCleanout
7
+
8
+ def initialize(mock,mock_control,expectation_builder)
9
+ @mock = mock
10
+ @mock_control = mock_control
11
+ @expectation_builder = expectation_builder
12
+ end
13
+
14
+ def method_missing(mname, *args, &block)
15
+ expectation = @expectation_builder.build_expectation(
16
+ :mock => @mock,
17
+ :method => mname,
18
+ :arguments => args,
19
+ :block => block)
20
+
21
+ @mock_control.add_expectation expectation
22
+ expectation
23
+ end
24
+ end
25
+
26
+ end
@@ -1,7 +1,7 @@
1
1
 
2
2
  module Hardmock #:nodoc:
3
3
  module MethodCleanout #:nodoc:
4
- SACRED_METHODS = %w|__id__ __send__ equal? object_id send nil? class kind_of? respond_to? inspect|
4
+ SACRED_METHODS = %w|__id__ __send__ equal? object_id send nil? class kind_of? respond_to? inspect method to_s instance_variables instance_eval ==|
5
5
 
6
6
  def self.included(base) #:nodoc:
7
7
  base.class_eval do
@@ -0,0 +1,182 @@
1
+
2
+ module Hardmock
3
+ # Mock is used to set expectations in your test. Most of the time you'll use
4
+ # <tt>#expects</tt> to create expectations.
5
+ #
6
+ # Aside from the scant few control methods (like +expects+, +trap+ and +_verify+)
7
+ # all calls made on a Mock instance will be immediately applied to the internal
8
+ # expectation mechanism.
9
+ #
10
+ # * If the method call was expected and all the parameters match properly, execution continues
11
+ # * If the expectation was configured with an expectation block, the block is invoked
12
+ # * If the expectation was set up to raise an error, the error is raised now
13
+ # * If the expectation was set up to return a value, it is returned
14
+ # * If the method call was _not_ expected, or the parameter values are wrong, an ExpectationError is raised.
15
+ class Mock
16
+ include Hardmock::MethodCleanout
17
+
18
+ # Create a new Mock instance with a name and a MockControl to support it.
19
+ # If not given, a MockControl is made implicitly for this Mock alone; this means
20
+ # expectations for this mock are not tied to other expectations in your test.
21
+ #
22
+ # It's not recommended to use a Mock directly; see Hardmock and
23
+ # Hardmock#create_mocks for the more wholistic approach.
24
+ def initialize(name, mock_control=nil)
25
+ @name = name
26
+ @control = mock_control || MockControl.new
27
+ @expectation_builder = ExpectationBuilder.new
28
+ end
29
+
30
+ def inspect
31
+ "<Mock #{@name}>"
32
+ end
33
+
34
+ # Begin declaring an expectation for this Mock.
35
+ #
36
+ # == Simple Examples
37
+ # Expect the +customer+ to be queried for +account+, and return <tt>"The
38
+ # Account"</tt>:
39
+ # @customer.expects.account.returns "The Account"
40
+ #
41
+ # Expect the +withdraw+ method to be called, and raise an exception when it
42
+ # is (see Expectation#raises for more info):
43
+ # @cash_machine.expects.withdraw(20,:dollars).raises("not enough money")
44
+ #
45
+ # Expect +customer+ to have its +user_name+ set
46
+ # @customer.expects.user_name = 'Big Boss'
47
+ #
48
+ # Expect +customer+ to have its +user_name+ set, and raise a RuntimeException when
49
+ # that happens:
50
+ # @customer.expects('user_name=', "Big Boss").raises "lost connection"
51
+ #
52
+ # Expect +evaluate+ to be passed a block, and when that happens, pass a value
53
+ # to the block (see Expectation#yields for more info):
54
+ # @cruncher.expects.evaluate.yields("some data").returns("some results")
55
+ #
56
+ #
57
+ # == Expectation Blocks
58
+ # To do special handling of expected method calls when they occur, you
59
+ # may pass a block to your expectation, like:
60
+ # @page_scraper.expects.handle_content do |address,request,status|
61
+ # assert_not_nil address, "Can't abide nil addresses"
62
+ # assert_equal "http-get", request.method, "Can only handle GET"
63
+ # assert status > 200 and status < 300, status, "Failed status"
64
+ # "Simulated results #{request.content.downcase}"
65
+ # end
66
+ # In this example, when <tt>page_scraper.handle_content</tt> is called, its
67
+ # three arguments are passed to the <i>expectation block</i> and evaluated
68
+ # using the above assertions. The last value in the block will be used
69
+ # as the return value for +handle_content+
70
+ #
71
+ # You may specify arguments to the expected method call, just like any normal
72
+ # expectation, and those arguments will be pre-validated before being passed
73
+ # to the expectation block. This is useful when you know all of the
74
+ # expected values but still need to do something programmatic.
75
+ #
76
+ # If the method being invoked on the mock accepts a block, that block will be
77
+ # passed to your expectation block as the last (or only) argument. Eg, the
78
+ # convenience method +yields+ can be replaced with the more explicit:
79
+ # @cruncher.expects.evaluate do |block|
80
+ # block.call "some data"
81
+ # "some results"
82
+ # end
83
+ #
84
+ # The result value of the expectation block becomes the return value for the
85
+ # expected method call. This can be overidden by using the +returns+ method:
86
+ # @cruncher.expects.evaluate do |block|
87
+ # block.call "some data"
88
+ # "some results"
89
+ # end.returns("the actual value")
90
+ #
91
+ # <b>Additionally</b>, the resulting value of the expectation block is stored
92
+ # in the +block_value+ field on the expectation. If you've saved a reference
93
+ # to your expectation, you may retrieve the block value once the expectation
94
+ # has been met.
95
+ #
96
+ # evaluation_event = @cruncher.expects.evaluate do |block|
97
+ # block.call "some data"
98
+ # "some results"
99
+ # end.returns("the actual value")
100
+ #
101
+ # result = @cruncher.evaluate do |input|
102
+ # puts input # => 'some data'
103
+ # end
104
+ # # result is 'the actual value'
105
+ #
106
+ # evaluation_event.block_value # => 'some results'
107
+ #
108
+ def expects(*args, &block)
109
+ expector = Expector.new(self,@control,@expectation_builder)
110
+ # If there are no args, we return the Expector
111
+ return expector if args.empty?
112
+ # If there ARE args, we set up the expectation right here and return it
113
+ expector.send(args.shift.to_sym, *args, &block)
114
+ end
115
+ alias_method :expect, :expects
116
+ # def expect(*args, &block) #:nodoc:
117
+ # raise DeprecationError.new("Please use 'expects' instead of 'expect'. Sorry about the inconvenience.")
118
+ # end
119
+
120
+ # Special-case convenience: #trap sets up an expectation for a method
121
+ # that will take a block. That block, when sent to the expected method, will
122
+ # be trapped and stored in the expectation's +block_value+ field.
123
+ # The Expectation#trigger method may then be used to invoke that block.
124
+ #
125
+ # Like +expects+, the +trap+ mechanism can be followed by +raises+ or +returns+.
126
+ #
127
+ # _Unlike_ +expects+, you may not use an expectation block with +trap+. If
128
+ # the expected method takes arguments in addition to the block, they must
129
+ # be specified in the arguments to the +trap+ call itself.
130
+ #
131
+ # == Example
132
+ #
133
+ # create_mocks :address_book, :editor_form
134
+ #
135
+ # # Expect a subscription on the :person_added event for @address_book:
136
+ # person_event = @address_book.trap.subscribe(:person_added)
137
+ #
138
+ # # The runtime code would look like:
139
+ # @address_book.subscribe :person_added do |person_name|
140
+ # @editor_form.name = person_name
141
+ # end
142
+ #
143
+ # # At this point, the expectation for 'subscribe' is met and the
144
+ # # block has been captured. But we're not done:
145
+ # @editor_form.expects.name = "David"
146
+ #
147
+ # # Now invoke the block we trapped earlier:
148
+ # person_event.trigger "David"
149
+ #
150
+ # verify_mocks
151
+ def trap(*args)
152
+ Trapper.new(self,@control,ExpectationBuilder.new)
153
+ end
154
+
155
+ def method_missing(mname,*args) #:nodoc:
156
+ block = nil
157
+ block = Proc.new if block_given?
158
+ @control.apply_method_call(self,mname,args,block)
159
+ end
160
+
161
+
162
+ def _control #:nodoc:
163
+ @control
164
+ end
165
+
166
+ def _name #:nodoc:
167
+ @name
168
+ end
169
+
170
+ # Verify that all expectations are fulfilled. NOTE: this method triggers
171
+ # validation on the _control_ for this mock, so all Mocks that share the
172
+ # MockControl with this instance will be included in the verification.
173
+ #
174
+ # <b>Only use this method if you are managing your own Mocks and their controls.</b>
175
+ #
176
+ # Normal usage of Hardmock doesn't require you to call this; let
177
+ # Hardmock#verify_mocks do it for you.
178
+ def _verify
179
+ @control.verify
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,45 @@
1
+ require 'hardmock/utils'
2
+
3
+ module Hardmock
4
+ class MockControl #:nodoc:
5
+ include Utils
6
+ attr_accessor :name
7
+
8
+ def initialize
9
+ @expectations = []
10
+ @disappointed = false
11
+ end
12
+
13
+ def happy?
14
+ @expectations.empty?
15
+ end
16
+
17
+ def disappointed?
18
+ @disappointed
19
+ end
20
+
21
+ def add_expectation(expectation)
22
+ @expectations << expectation
23
+ end
24
+
25
+ def apply_method_call(mock,mname,args,block)
26
+ # Are we even expecting any sort of call?
27
+ if happy?
28
+ @disappointed = true
29
+ raise ExpectationError.new("Surprise call to #{format_method_call_string(mock,mname,args)}")
30
+ end
31
+
32
+ begin
33
+ @expectations.shift.apply_method_call(mock,mname,args,block)
34
+ rescue Exception => ouch
35
+ @disappointed = true
36
+ raise ouch
37
+ end
38
+ end
39
+
40
+ def verify
41
+ @disappointed = !happy?
42
+ raise VerifyError.new("Unmet expectations", @expectations) unless happy?
43
+ end
44
+ end
45
+ end