flexmock 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/CHANGELOG +10 -1
  2. data/README +390 -209
  3. data/Rakefile +31 -10
  4. data/doc/GoogleExample.rdoc +275 -0
  5. data/doc/releases/flexmock-0.6.0.rdoc +136 -0
  6. data/lib/flexmock.rb +3 -1160
  7. data/lib/flexmock/argument_matchers.rb +57 -0
  8. data/lib/flexmock/argument_types.rb +42 -0
  9. data/lib/flexmock/base.rb +22 -0
  10. data/lib/flexmock/composite.rb +10 -0
  11. data/lib/flexmock/core.rb +206 -0
  12. data/lib/flexmock/core_class_methods.rb +92 -0
  13. data/lib/flexmock/default_framework_adapter.rb +31 -0
  14. data/lib/flexmock/expectation.rb +334 -0
  15. data/lib/flexmock/expectation_director.rb +59 -0
  16. data/lib/flexmock/mock_container.rb +159 -0
  17. data/lib/flexmock/noop.rb +13 -0
  18. data/lib/flexmock/partial_mock.rb +226 -0
  19. data/lib/flexmock/recorder.rb +71 -0
  20. data/lib/flexmock/rspec.rb +34 -0
  21. data/lib/flexmock/test_unit.rb +32 -0
  22. data/lib/flexmock/test_unit_integration.rb +53 -0
  23. data/lib/flexmock/validators.rb +77 -0
  24. data/test/rspec_integration/integration_spec.rb +36 -0
  25. data/test/test_container_methods.rb +119 -0
  26. data/test/test_default_framework_adapter.rb +39 -0
  27. data/test/test_example.rb +1 -1
  28. data/test/test_extended_should_receive.rb +63 -0
  29. data/test/test_mock.rb +1 -1
  30. data/test/test_naming.rb +1 -1
  31. data/test/{test_any_instance.rb → test_new_instances.rb} +15 -8
  32. data/test/{test_stubbing.rb → test_partial_mock.rb} +44 -44
  33. data/test/test_record_mode.rb +1 -1
  34. data/test/test_samples.rb +6 -8
  35. data/test/test_should_receive.rb +7 -3
  36. data/test/test_tu_integration.rb +1 -1
  37. data/test/test_unit_integration/test_auto_test_unit.rb +34 -0
  38. metadata +30 -5
  39. data/test/test_class_interception.rb +0 -140
@@ -0,0 +1,334 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #---
4
+ # Copyright 2003, 2004, 2005, 2006, 2007 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 'flexmock/noop'
13
+
14
+ class FlexMock
15
+
16
+ ####################################################################
17
+ # An Expectation is returned from each +should_receive+ message sent
18
+ # to mock object. Each expectation records how a message matching
19
+ # the message name (argument to +should_receive+) and the argument
20
+ # list (given by +with+) should behave. Mock expectations can be
21
+ # recorded by chaining the declaration methods defined in this
22
+ # class.
23
+ #
24
+ # For example:
25
+ #
26
+ # mock.should_receive(:meth).with(args).and_returns(result)
27
+ #
28
+ class Expectation
29
+
30
+ attr_reader :expected_args, :order_number
31
+ attr_accessor :mock
32
+
33
+ # Create an expectation for a method named +sym+.
34
+ def initialize(mock, sym)
35
+ @mock = mock
36
+ @sym = sym
37
+ @expected_args = nil
38
+ @count_validators = []
39
+ @count_validator_class = ExactCountValidator
40
+ @actual_count = 0
41
+ @return_value = nil
42
+ @return_block = lambda { @return_value }
43
+ @order_number = nil
44
+ end
45
+
46
+ def to_s
47
+ FlexMock.format_args(@sym, @expected_args)
48
+ end
49
+
50
+ # Verify the current call with the given arguments matches the
51
+ # expectations recorded in this object.
52
+ def verify_call(*args)
53
+ validate_order
54
+ @actual_count += 1
55
+ @return_block.call(*args)
56
+ end
57
+
58
+ # Is this expectation eligible to be called again? It is eligible
59
+ # only if all of its count validators agree that it is eligible.
60
+ def eligible?
61
+ @count_validators.all? { |v| v.eligible?(@actual_count) }
62
+ end
63
+
64
+ # Validate that the order
65
+ def validate_order
66
+ return if @order_number.nil?
67
+ FlexMock.check("method #{to_s} called out of order " +
68
+ "(expected order #{@order_number}, was #{@mock.mock_current_order})") {
69
+ @order_number >= @mock.mock_current_order
70
+ }
71
+ @mock.mock_current_order = @order_number
72
+ end
73
+ private :validate_order
74
+
75
+ # Validate the correct number of calls have been made. Called by
76
+ # the teardown process.
77
+ def mock_verify
78
+ @count_validators.each do |v|
79
+ v.validate(@actual_count)
80
+ end
81
+ end
82
+
83
+ # Does the argument list match this expectation's argument
84
+ # specification.
85
+ def match_args(args)
86
+ # TODO: Rethink this:
87
+ # return false if @expected_args.nil?
88
+ return true if @expected_args.nil?
89
+ return false if args.size != @expected_args.size
90
+ (0...args.size).all? { |i| match_arg(@expected_args[i], args[i]) }
91
+ end
92
+
93
+ # Does the expected argument match the corresponding actual value.
94
+ def match_arg(expected, actual)
95
+ expected === actual ||
96
+ expected == actual ||
97
+ ( Regexp === expected && expected === actual.to_s )
98
+ end
99
+
100
+ # Declare that the method should expect the given argument list.
101
+ def with(*args)
102
+ @expected_args = args
103
+ self
104
+ end
105
+
106
+ # Declare that the method should be called with no arguments.
107
+ def with_no_args
108
+ with
109
+ end
110
+
111
+ # Declare that the method can be called with any number of
112
+ # arguments of any type.
113
+ def with_any_args
114
+ @expected_args = nil
115
+ self
116
+ end
117
+
118
+ # :call-seq:
119
+ # and_return(value)
120
+ # and_return(value, value, ...)
121
+ # and_return { |*args| code }
122
+ #
123
+ # Declare that the method returns a particular value (when the
124
+ # argument list is matched).
125
+ #
126
+ # * If a single value is given, it will be returned for all matching
127
+ # calls.
128
+ # * If multiple values are given, each value will be returned in turn for
129
+ # each successive call. If the number of matching calls is greater
130
+ # than the number of values, the last value will be returned for
131
+ # the extra matching calls.
132
+ # * If a block is given, it is evaluated on each call and its
133
+ # value is returned.
134
+ #
135
+ # For example:
136
+ #
137
+ # mock.should_receive(:f).returns(12) # returns 12
138
+ #
139
+ # mock.should_receive(:f).with(String). # returns an
140
+ # returns { |str| str.upcase } # upcased string
141
+ #
142
+ # +and_return+ is an alias for +returns+.
143
+ #
144
+ def and_return(*args, &block)
145
+ @return_block =
146
+ if block_given?
147
+ block
148
+ else
149
+ lambda { args.size == 1 ? args.first : args.shift }
150
+ end
151
+ self
152
+ end
153
+ alias :returns :and_return # :nodoc:
154
+
155
+ # Declare that the method may be called any number of times.
156
+ def zero_or_more_times
157
+ at_least.never
158
+ end
159
+
160
+ # Declare that the method is called +limit+ times with the
161
+ # declared argument list. This may be modified by the +at_least+
162
+ # and +at_most+ declarators.
163
+ def times(limit)
164
+ @count_validators << @count_validator_class.new(self, limit) unless limit.nil?
165
+ @count_validator_class = ExactCountValidator
166
+ self
167
+ end
168
+
169
+ # Declare that the method is never expected to be called with the
170
+ # given argument list. This may be modified by the +at_least+ and
171
+ # +at_most+ declarators.
172
+ def never
173
+ times(0)
174
+ end
175
+
176
+ # Declare that the method is expected to be called exactly once
177
+ # with the given argument list. This may be modified by the
178
+ # +at_least+ and +at_most+ declarators.
179
+ def once
180
+ times(1)
181
+ end
182
+
183
+ # Declare that the method is expected to be called exactly twice
184
+ # with the given argument list. This may be modified by the
185
+ # +at_least+ and +at_most+ declarators.
186
+ def twice
187
+ times(2)
188
+ end
189
+
190
+ # Modifies the next call count declarator (+times+, +never+,
191
+ # +once+ or +twice+) so that the declarator means the method is
192
+ # called at least that many times.
193
+ #
194
+ # E.g. method f must be called at least twice:
195
+ #
196
+ # mock.should_receive(:f).at_least.twice
197
+ #
198
+ def at_least
199
+ @count_validator_class = AtLeastCountValidator
200
+ self
201
+ end
202
+
203
+ # Modifies the next call count declarator (+times+, +never+,
204
+ # +once+ or +twice+) so that the declarator means the method is
205
+ # called at most that many times.
206
+ #
207
+ # E.g. method f must be called no more than twice
208
+ #
209
+ # mock.should_receive(:f).at_most.twice
210
+ #
211
+ def at_most
212
+ @count_validator_class = AtMostCountValidator
213
+ self
214
+ end
215
+
216
+ # Declare that the given method must be called in order. All
217
+ # ordered method calls must be received in the order specified by
218
+ # the ordering of the +should_receive+ messages. Receiving a
219
+ # methods out of the specified order will cause a test failure.
220
+ #
221
+ # If the user needs more fine control over ordering
222
+ # (e.g. specifying that a group of messages may be received in any
223
+ # order as long as they all come after another group of messages),
224
+ # a _group_ _name_ may be specified in the +ordered+ calls. All
225
+ # messages within the same group may be received in any order.
226
+ #
227
+ # For example, in the following, messages +flip+ and +flop+ may be
228
+ # received in any order (because they are in the same group), but
229
+ # must occur strictly after +start+ but before +end+. The message
230
+ # +any_time+ may be received at any time because it is not
231
+ # ordered.
232
+ #
233
+ # m = FlexMock.new
234
+ # m.should_receive(:any_time)
235
+ # m.should_receive(:start).ordered
236
+ # m.should_receive(:flip).ordered(:flip_flop_group)
237
+ # m.should_receive(:flop).ordered(:flip_flop_group)
238
+ # m.should_receive(:end).ordered
239
+ #
240
+ def ordered(group_name=nil)
241
+ if group_name.nil?
242
+ @order_number = @mock.mock_allocate_order
243
+ elsif (num = @mock.mock_groups[group_name])
244
+ @order_number = num
245
+ else
246
+ @order_number = @mock.mock_allocate_order
247
+ @mock.mock_groups[group_name] = @order_number
248
+ end
249
+ self
250
+ end
251
+ end
252
+
253
+ ##########################################################################
254
+ # A composite expectation allows several expectations to be grouped into a
255
+ # single composite and then apply the same constraints to all expectations
256
+ # in the group.
257
+ class CompositeExpectation
258
+
259
+ # Initialize the composite expectation.
260
+ def initialize
261
+ @expectations = []
262
+ end
263
+
264
+ # Add an expectation to the composite.
265
+ def add(expectation)
266
+ @expectations << expectation
267
+ end
268
+
269
+ # Apply the constraint method to all expectations in the composite.
270
+ def method_missing(sym, *args, &block)
271
+ @expectations.each do |expectation|
272
+ expectation.send(sym, *args, &block)
273
+ end
274
+ self
275
+ end
276
+
277
+ # The following methods return a value, so we make an arbitrary choice
278
+ # and return the value for the first expectation in the composite.
279
+
280
+ # Return the order number of the first expectation in the list.
281
+ def order_number
282
+ @expectations.first.order_number
283
+ end
284
+
285
+ # Return the associated mock object.
286
+ def mock
287
+ @expectations.first.mock
288
+ end
289
+
290
+ # Start a new method expectation. The following constraints will be
291
+ # applied to the new expectation.
292
+ def should_receive(*args, &block)
293
+ @expectations.first.mock.should_receive(*args, &block)
294
+ end
295
+
296
+ # Return a string representations
297
+ def to_s
298
+ if @expectations.size > 1
299
+ "[" + @expectations.collect { |e| e.to_s }.join(', ') + "]"
300
+ else
301
+ @expectations.first.to_s
302
+ end
303
+ end
304
+ end
305
+
306
+ ##########################################################################
307
+ # An expectation recorder records any expectations received and plays them
308
+ # back on demand. This is used to collect the expectations in the blockless
309
+ # version of the new_instances call.
310
+ #
311
+ class ExpectationRecorder
312
+
313
+ # Initialize the recorder.
314
+ def initialize
315
+ @expectations = []
316
+ end
317
+
318
+ # Save any incoming messages to be played back later.
319
+ def method_missing(sym, *args, &block)
320
+ @expectations << [sym, args, block]
321
+ self
322
+ end
323
+
324
+ # Apply the recorded messages to the given object in a chaining fashion
325
+ # (i.e. the result of the previous call is used as the target of the next
326
+ # call).
327
+ def apply(mock)
328
+ obj = mock
329
+ @expectations.each do |sym, args, block|
330
+ obj = obj.send(sym, *args, &block)
331
+ end
332
+ end
333
+ end
334
+ end
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #---
4
+ # Copyright 2003, 2004, 2005, 2006, 2007 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 'flexmock/noop'
13
+
14
+ class FlexMock
15
+
16
+ ####################################################################
17
+ # The expectation director is responsible for routing calls to the
18
+ # correct expectations for a given argument list.
19
+ #
20
+ class ExpectationDirector
21
+
22
+ # Create an ExpectationDirector for a mock object.
23
+ def initialize(sym)
24
+ @sym = sym
25
+ @expectations = []
26
+ @expected_order = nil
27
+ end
28
+
29
+ # Invoke the expectations for a given set of arguments.
30
+ #
31
+ # First, look for an expectation that matches the arguements and
32
+ # is eligible to be called. Failing that, look for a expectation
33
+ # that matches the arguments (at this point it will be ineligible,
34
+ # but at least we will get a good failure message). Finally,
35
+ # check for expectations that don't have any argument matching
36
+ # criteria.
37
+ def call(*args)
38
+ exp = @expectations.find { |e| e.match_args(args) && e.eligible? } ||
39
+ @expectations.find { |e| e.match_args(args) }
40
+ FlexMock.check("no matching handler found for " +
41
+ FlexMock.format_args(@sym, args)) { ! exp.nil? }
42
+ exp.verify_call(*args)
43
+ end
44
+
45
+ # Append an expectation to this director.
46
+ def <<(expectation)
47
+ @expectations << expectation
48
+ end
49
+
50
+ # Do the post test verification for this directory. Check all the
51
+ # expectations.
52
+ def mock_verify
53
+ @expectations.each do |exp|
54
+ exp.mock_verify
55
+ end
56
+ end
57
+ end
58
+
59
+ end
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #---
4
+ # Copyright 2003, 2004, 2005, 2006, 2007 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 'flexmock/noop'
13
+
14
+ class FlexMock
15
+
16
+ ####################################################################
17
+ # Mock container methods
18
+ #
19
+ # Include this module in to get integration with FlexMock. When
20
+ # this module is included, mocks may be created with a simple call
21
+ # to the +flexmock+ method. Mocks created with via the method call
22
+ # will automatically be verified in the teardown of the test case.
23
+ #
24
+ module MockContainer
25
+ # Do the flexmock specific teardown stuff. If you need finer control,
26
+ # you can use either +flexmock_verify+ or +flexmock_close+.
27
+ def flexmock_teardown
28
+ flexmock_verify if passed?
29
+ ensure
30
+ flexmock_close
31
+ end
32
+
33
+ # Perform verification on all mocks in the container.
34
+ def flexmock_verify
35
+ @flexmock_created_mocks ||= []
36
+ @flexmock_created_mocks.each do |m|
37
+ m.mock_verify
38
+ end
39
+ end
40
+
41
+ # Close all the mock objects in the container. Closing a mock object
42
+ # restores any original behavior that was displaced by the mock.
43
+ def flexmock_close
44
+ @flexmock_created_mocks ||= []
45
+ @flexmock_created_mocks.each do |m|
46
+ m.mock_teardown
47
+ end
48
+ @flexmock_created_mocks = []
49
+ end
50
+
51
+ # Create a mocking object in the FlexMock framework. The +flexmock+
52
+ # method has a number of options available, depending on just what
53
+ # kind of mocking object your require. Mocks created via +flexmock+
54
+ # will be automatically verify during the teardown phase of your
55
+ # test framework.
56
+ #
57
+ # :call-seq:
58
+ # flexmock() { |mock| ... }
59
+ # flexmock(name) { |mock| ... }
60
+ # flexmock(expect_hash) { |mock| ... }
61
+ # flexmock(name, expect_hash) { |mock| ... }
62
+ # flexmock(real_object) { |mock| ... }
63
+ # flexmock(real_object, name) { |mock| ... }
64
+ # flexmock(real_object, name, expect_hash) { |mock| ... }
65
+ # flexmock(:base, string, name, expect_hash) { |mock| ... }
66
+ #
67
+ # name ::
68
+ # Name of the mock object. If no name is given, "unknown" is used for
69
+ # full mocks and "flexmock(<em>real_object</em>)" is used for partial
70
+ # mocks.
71
+ #
72
+ # expect_hash ::
73
+ # Hash table of method names and values. Each method/value pair is
74
+ # used to setup a simple expectation so that if the mock object
75
+ # receives a message matching an entry in the table, it returns
76
+ # the associated value. No argument our call count constraints are
77
+ # added. Using an expect_hash is identical to calling:
78
+ #
79
+ # mock.should_receive(method_name).and_return(value)
80
+ #
81
+ # for each of the method/value pairs in the hash.
82
+ #
83
+ # real_object ::
84
+ # If a real object is given, then a partial mock is constructed
85
+ # using the real_object as a base. Partial mocks (formally referred
86
+ # to as stubs) behave as a mock object when an expectation is matched,
87
+ # and otherwise will behave like the original object. This is useful
88
+ # when you want to use a real object for testing, but need to mock out
89
+ # just one or two methods.
90
+ #
91
+ # :base ::
92
+ # Forces the following argument to be used as the base of a
93
+ # partial mock object. This explicit tag is only needed if you
94
+ # want to use a string or a symbol as the mock base (string and
95
+ # symbols would normally be interpretted as the mock name).
96
+ #
97
+ # &block ::
98
+ # If a block is given, then the mock object is passed to the block and
99
+ # expectations may be configured within the block.
100
+ #
101
+ def flexmock(*args)
102
+ name = nil
103
+ quick_defs = {}
104
+ stub_target = nil
105
+ while ! args.empty?
106
+ case args.first
107
+ when :base
108
+ args.shift
109
+ stub_target = args.shift
110
+ when String, Symbol
111
+ name = args.shift.to_s
112
+ when Hash
113
+ quick_defs = args.shift
114
+ else
115
+ stub_target = args.shift
116
+ end
117
+ end
118
+ if stub_target
119
+ mock = flexmock_make_stub(stub_target, name)
120
+ else
121
+ mock = FlexMock.new(name || "unknown")
122
+ end
123
+ flexmock_quick_define(mock, quick_defs)
124
+ yield(mock) if block_given?
125
+ flexmock_remember(mock)
126
+ mock
127
+ end
128
+ alias flexstub flexmock
129
+
130
+ private
131
+
132
+ # Create a PartialMock for the given object. Use +name+ as the name
133
+ # of the mock object.
134
+ def flexmock_make_stub(obj, name)
135
+ name ||= "flexmock(#{obj.class.to_s})"
136
+ obj.instance_eval {
137
+ @flexmock_proxy ||= PartialMock.new(obj, FlexMock.new(name))
138
+ }
139
+ obj.instance_variable_get("@flexmock_proxy")
140
+ end
141
+
142
+ # Create a set of mocked methods from a hash.
143
+ def flexmock_quick_define(mock, defs)
144
+ defs.each do |method, value|
145
+ mock.should_receive(method).and_return(value)
146
+ end
147
+ mock
148
+ end
149
+
150
+ # Remember the mock object / stub in the mock container.
151
+ def flexmock_remember(mocking_object)
152
+ @flexmock_created_mocks ||= []
153
+ @flexmock_created_mocks << mocking_object
154
+ mocking_object.mock_container = self
155
+ mocking_object
156
+ end
157
+ end
158
+
159
+ end