flexmock 0.6.4 → 0.7.0

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.
@@ -35,13 +35,18 @@ class FlexMock
35
35
  # check for expectations that don't have any argument matching
36
36
  # criteria.
37
37
  def call(*args)
38
- exp = @expectations.find { |e| e.match_args(args) && e.eligible? } ||
39
- @expectations.find { |e| e.match_args(args) }
38
+ exp = find_expectation(*args)
40
39
  FlexMock.check("no matching handler found for " +
41
- FlexMock.format_args(@sym, args)) { ! exp.nil? }
40
+ FlexMock.format_args(@sym, args)) { ! exp.nil? }
42
41
  exp.verify_call(*args)
43
42
  end
44
43
 
44
+ # Find an expectation matching the given arguments.
45
+ def find_expectation(*args)
46
+ @expectations.find { |e| e.match_args(args) && e.eligible? } ||
47
+ @expectations.find { |e| e.match_args(args) }
48
+ end
49
+
45
50
  # Append an expectation to this director.
46
51
  def <<(expectation)
47
52
  @expectations << expectation
@@ -49,9 +54,9 @@ class FlexMock
49
54
 
50
55
  # Do the post test verification for this directory. Check all the
51
56
  # expectations.
52
- def mock_verify
57
+ def flexmock_verify
53
58
  @expectations.each do |exp|
54
- exp.mock_verify
59
+ exp.flexmock_verify
55
60
  end
56
61
  end
57
62
  end
@@ -10,6 +10,7 @@
10
10
  #+++
11
11
 
12
12
  require 'flexmock/noop'
13
+ require 'flexmock/argument_types'
13
14
 
14
15
  class FlexMock
15
16
 
@@ -22,6 +23,8 @@ class FlexMock
22
23
  # verified in the teardown of the test case.
23
24
  #
24
25
  module MockContainer
26
+ include Ordering
27
+
25
28
  # Do the flexmock specific teardown stuff. If you need finer control,
26
29
  # you can use either +flexmock_verify+ or +flexmock_close+.
27
30
  def flexmock_teardown
@@ -34,7 +37,7 @@ class FlexMock
34
37
  def flexmock_verify
35
38
  @flexmock_created_mocks ||= []
36
39
  @flexmock_created_mocks.each do |m|
37
- m.mock_verify
40
+ m.flexmock_verify
38
41
  end
39
42
  end
40
43
 
@@ -43,7 +46,7 @@ class FlexMock
43
46
  def flexmock_close
44
47
  @flexmock_created_mocks ||= []
45
48
  @flexmock_created_mocks.each do |m|
46
- m.mock_teardown
49
+ m.flexmock_teardown
47
50
  end
48
51
  @flexmock_created_mocks = []
49
52
  end
@@ -134,68 +137,180 @@ class FlexMock
134
137
  raise UsageError, "a block is required in safe mode" if safe_mode && ! block_given?
135
138
 
136
139
  if domain_obj
137
- mock = flexmock_make_partial_proxy(domain_obj, name, safe_mode)
140
+ mock = ContainerHelper.make_partial_proxy(self, domain_obj, name, safe_mode)
138
141
  result = domain_obj
139
142
  elsif model_class
140
- id = MockContainer.next_id
141
- result = mock = FlexMock.new("#{model_class}_#{id}")
143
+ id = ContainerHelper.next_id
144
+ result = mock = FlexMock.new("#{model_class}_#{id}", self)
142
145
  else
143
- result = mock = FlexMock.new(name || "unknown")
146
+ result = mock = FlexMock.new(name || "unknown", self)
144
147
  end
145
- flexmock_quick_define(mock, quick_defs)
148
+ mock.should_receive(quick_defs)
146
149
  yield(mock) if block_given?
147
150
  flexmock_remember(mock)
148
- flexmock_mock_model_methods(mock, model_class, id) if model_class
151
+ ContainerHelper.add_model_methods(mock, model_class, id) if model_class
149
152
  result
150
153
  end
151
154
  alias flexstub flexmock
152
155
 
153
- private
154
-
156
+ # Remember the mock object / stub in the mock container.
157
+ def flexmock_remember(mocking_object)
158
+ @flexmock_created_mocks ||= []
159
+ @flexmock_created_mocks << mocking_object
160
+ mocking_object.flexmock_container = self
161
+ mocking_object
162
+ end
163
+ end
164
+
165
+ # #################################################################
166
+ # Helper methods for mock containers. MockContainer is a module
167
+ # that is designed to be mixed into other classes, particularly
168
+ # testing framework test cases. Since we don't want to pollute the
169
+ # method namespace of the class that mixes in MockContainer, a
170
+ # number of MockContainer methods were moved into ContainerHelper to
171
+ # to isoloate the names.
172
+ #
173
+ class MockContainerHelper
174
+ include FlexMock::ArgumentTypes
175
+
176
+ # Return the next id for mocked models.
177
+ def next_id
178
+ @id_counter ||= 0
179
+ @id_counter += 1
180
+ end
181
+
182
+ # :call-seq:
183
+ # parse_should_args(args) { |symbol| ... }
184
+ #
185
+ # This method provides common handling for the various should_receive
186
+ # argument lists. It sorts out the differences between symbols, arrays and
187
+ # hashes, and identifies the method names specified by each. As each
188
+ # method name is identified, create a mock expectation for it using the
189
+ # supplied block.
190
+ def parse_should_args(mock, args, &block) # :nodoc:
191
+ result = CompositeExpectation.new
192
+ args.each do |arg|
193
+ case arg
194
+ when Hash
195
+ arg.each do |k,v|
196
+ exp = build_demeter_chain(mock, k, &block).and_return(v)
197
+ result.add(exp)
198
+ end
199
+ when Symbol, String
200
+ result.add(build_demeter_chain(mock, arg, &block))
201
+ end
202
+ end
203
+ result
204
+ end
205
+
155
206
  # Automatically add mocks for some common methods in ActiveRecord
156
207
  # models.
157
- def flexmock_mock_model_methods(mock, model_class, id)
208
+ def add_model_methods(mock, model_class, id)
209
+ container = mock.flexmock_container
158
210
  mock.should_receive(
159
211
  :id => id,
160
212
  :to_params => id.to_s,
161
213
  :new_record? => false,
162
214
  :class => model_class,
163
- :errors => flexmock("errors", :count => 0))
215
+ :errors => container.flexmock("errors", :count => 0))
164
216
  mock.should_receive(:is_a?).with(any).and_return { |other|
165
217
  other == model_class
166
218
  }
219
+ mock.should_receive(:instance_of?).with(any).and_return { |other|
220
+ other == model_class
221
+ }
222
+ mock.should_receive(:kind_of?).with(any).and_return { |other|
223
+ model_class.ancestors.include?(other)
224
+ }
167
225
  end
168
226
 
169
227
  # Create a PartialMockProxy for the given object. Use +name+ as
170
228
  # the name of the mock object.
171
- def flexmock_make_partial_proxy(obj, name, safe_mode)
229
+ def make_partial_proxy(container, obj, name, safe_mode)
172
230
  name ||= "flexmock(#{obj.class.to_s})"
173
231
  obj.instance_eval {
174
- @flexmock_proxy ||= PartialMockProxy.new(obj, FlexMock.new(name), safe_mode)
232
+ mock = FlexMock.new(name, container)
233
+ @flexmock_proxy ||= PartialMockProxy.new(obj, mock, safe_mode)
175
234
  }
176
235
  obj.instance_variable_get("@flexmock_proxy")
177
236
  end
178
237
 
179
- # Create a set of mocked methods from a hash.
180
- def flexmock_quick_define(mock, defs)
181
- defs.each do |method, value|
182
- mock.should_receive(method).and_return(value)
238
+ private
239
+
240
+ # Build the chain of mocks for demeter style mocking.
241
+ #
242
+ # Warning: Nasty code ahead.
243
+ #
244
+ # This method builds a chain of mocks to support demeter style
245
+ # mocking. Given a mock chain of "first.second.third.last", we
246
+ # must build a chain of mock methods that return the next mock in
247
+ # the chain. The expectation for the last method of the chain is
248
+ # returned as the result of the method.
249
+ #
250
+ # Things to consider:
251
+ #
252
+ # (1) The expectation for the "first" method must be created by
253
+ # the proper mechanism, which is supplied by the block parameter
254
+ # "block". In other words, first expectation is created by
255
+ # calling the block. (This allows us to create expectations on
256
+ # both pure mocks and partial mocks, with the block handling the
257
+ # details).
258
+ #
259
+ # (2) Although the first mock is arbitrary, the remaining mocks in
260
+ # the chain will always be pure mocks created specifically for
261
+ # this purpose.
262
+ #
263
+ # (3) The expectations for all methods but the last in the chain
264
+ # will be setup to expect no parameters and to return the next
265
+ # mock in the chain.
266
+ #
267
+ # (4) It could very well be the case that several demeter chains
268
+ # will be defined on a single mock object, and those chains could
269
+ # share some of the same methods (e.g. "mock.one.two.read" and
270
+ # "mock.one.two.write" both share the methods "one" and "two").
271
+ # It is important that the shared methods return the same mocks in
272
+ # both chains.
273
+ #
274
+ def build_demeter_chain(mock, arg, &block)
275
+ container = mock.flexmock_container
276
+ names = arg.to_s.split('.')
277
+ check_method_names(names)
278
+ exp = nil
279
+ next_exp = lambda { |n| block.call(n) }
280
+ loop do
281
+ method_name = names.shift.to_sym
282
+ exp = mock.flexmock_find_expectation(method_name)
283
+ need_new_exp = exp.nil? || names.empty?
284
+ exp = next_exp.call(method_name) if need_new_exp
285
+ break if names.empty?
286
+ if need_new_exp
287
+ mock = container.flexmock("demeter_#{method_name}")
288
+ exp.with_no_args.and_return(mock)
289
+ else
290
+ mock = exp._return_value([])
291
+ end
292
+ check_proper_mock(mock, method_name)
293
+ next_exp = lambda { |n| mock.should_receive(n) }
183
294
  end
184
- mock
295
+ exp
185
296
  end
186
297
 
187
- # Remember the mock object / stub in the mock container.
188
- def flexmock_remember(mocking_object)
189
- @flexmock_created_mocks ||= []
190
- @flexmock_created_mocks << mocking_object
191
- mocking_object.mock_container = self
192
- mocking_object
298
+ # Check that the given mock is a real FlexMock mock.
299
+ def check_proper_mock(mock, method_name)
300
+ unless mock.kind_of?(FlexMock)
301
+ fail FlexMock::UsageError,
302
+ "Conflicting mock declaration for '#{method_name}' in demeter style mock"
303
+ end
193
304
  end
194
-
195
- def MockContainer.next_id
196
- @id_counter ||= 0
197
- @id_counter += 1
305
+
306
+ # Check that all the names in the list are valid method names.
307
+ def check_method_names(names)
308
+ names.each do |name|
309
+ fail FlexMock::UsageError, "Ill-formed method name '#{name}'" if
310
+ name !~ /^[A-Za-z_][A-Za-z0-9_]*[=!?]?$/
311
+ end
198
312
  end
199
313
  end
200
314
 
315
+ ContainerHelper = MockContainerHelper.new
201
316
  end
@@ -0,0 +1,51 @@
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
+ class FlexMock
13
+
14
+ # #################################################################
15
+ # The ordering module contains the methods and data structures used
16
+ # to determine proper orderring of mocked calls. By providing the
17
+ # functionality in a module, a individual mock object can order its
18
+ # own calls, and the container can provide ordering at a global
19
+ # level.
20
+ module Ordering
21
+
22
+ # Allocate the next available order number.
23
+ def flexmock_allocate_order
24
+ @flexmock_allocated_order ||= 0
25
+ @flexmock_allocated_order += 1
26
+ end
27
+
28
+ # Hash of groups defined in this ordering.
29
+ def flexmock_groups
30
+ @flexmock_groups ||= {}
31
+ end
32
+
33
+ # Current order number in this ordering.
34
+ def flexmock_current_order
35
+ @flexmock_current_order ||= 0
36
+ end
37
+
38
+ # Set the current order for this ordering.
39
+ def flexmock_current_order=(value)
40
+ @flexmock_current_order = value
41
+ end
42
+
43
+ def flexmock_validate_order(method_name, order_number)
44
+ FlexMock.check("method #{method_name} called out of order " +
45
+ "(expected order #{order_number}, was #{flexmock_current_order})") {
46
+ order_number >= self.flexmock_current_order
47
+ }
48
+ self.flexmock_current_order = order_number
49
+ end
50
+ end
51
+ end
@@ -24,15 +24,16 @@ class FlexMock
24
24
  # (e.g. remove instance variables and mock singleton methods).
25
25
  #
26
26
  class PartialMockProxy
27
- attr_reader :mock, :mock_groups
28
- attr_accessor :mock_current_order, :mock_container
27
+ include Ordering
28
+
29
+ attr_reader :mock
29
30
 
30
31
  # The following methods are added to partial mocks so that they
31
32
  # can act like a mock.
32
33
 
33
34
  MOCK_METHODS = [
34
- :should_receive, :new_instances, :any_instance,
35
- :mock, :mock_teardown, :mock_verify
35
+ :should_receive, :new_instances,
36
+ :flexmock_get, :flexmock_teardown, :flexmock_verify
36
37
  ]
37
38
 
38
39
  # Initialize a PartialMockProxy object.
@@ -41,9 +42,6 @@ class FlexMock
41
42
  @mock = mock
42
43
  @method_definitions = {}
43
44
  @methods_proxied = []
44
- @allocated_order = 0
45
- @mock_current_order = 0
46
- @mock_groups = {}
47
45
  unless safe_mode
48
46
  MOCK_METHODS.each do |sym|
49
47
  add_mock_method(@obj, sym)
@@ -51,6 +49,11 @@ class FlexMock
51
49
  end
52
50
  end
53
51
 
52
+ # Get the mock object for the partial mock.
53
+ def flexmock_get
54
+ @mock
55
+ end
56
+
54
57
  # :call-seq:
55
58
  # should_receive(:method_name)
56
59
  # should_receive(:method1, method2, ...)
@@ -72,7 +75,7 @@ class FlexMock
72
75
  #
73
76
  # See Expectation for a list of declarators that can be used.
74
77
  def should_receive(*args)
75
- FlexMock.should_receive(args) do |sym|
78
+ ContainerHelper.parse_should_args(@mock, args) do |sym|
76
79
  unless @methods_proxied.include?(sym)
77
80
  hide_existing_method(sym)
78
81
  end
@@ -115,7 +118,7 @@ class FlexMock
115
118
  allocators.each do |m|
116
119
  self.should_receive(m).and_return { |*args|
117
120
  new_obj = invoke_original(m, args)
118
- mock = mock_container.flexmock(new_obj)
121
+ mock = flexmock_container.flexmock(new_obj)
119
122
  block.call(mock) if block_given?
120
123
  result.apply(mock)
121
124
  new_obj
@@ -124,13 +127,6 @@ class FlexMock
124
127
  result
125
128
  end
126
129
 
127
- # any_instance is present for backwards compatibility with version 0.5.0.
128
- # @deprecated
129
- def any_instance(&block)
130
- $stderr.puts "any_instance is deprecated, use new_instances instead."
131
- new_instances(&block)
132
- end
133
-
134
130
  # Invoke the original definition of method on the object supported by
135
131
  # the stub.
136
132
  def invoke_original(method, args)
@@ -141,12 +137,12 @@ class FlexMock
141
137
 
142
138
  # Verify that the mock has been properly called. After verification,
143
139
  # detach the mocking infrastructure from the existing object.
144
- def mock_verify
145
- @mock.mock_verify
140
+ def flexmock_verify
141
+ @mock.flexmock_verify
146
142
  end
147
143
 
148
144
  # Remove all traces of the mocking framework from the existing object.
149
- def mock_teardown
145
+ def flexmock_teardown
150
146
  if ! detached?
151
147
  @methods_proxied.each do |method_name|
152
148
  remove_current_method(method_name)
@@ -157,9 +153,14 @@ class FlexMock
157
153
  end
158
154
  end
159
155
 
160
- # Allocation a new order number from the mock.
161
- def mock_allocate_order
162
- @allocated_order += 1
156
+ # Forward to the mock's container.
157
+ def flexmock_container
158
+ @mock.flexmock_container
159
+ end
160
+
161
+ # Set the proxy's mock container. This set value is ignored
162
+ # because the proxy always uses the container of its mock.
163
+ def flexmock_container=(container)
163
164
  end
164
165
 
165
166
  private
@@ -0,0 +1,34 @@
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
+ class FlexMock
13
+
14
+ # Provide a common failure assertion.
15
+ module FailureAssertion
16
+ private
17
+
18
+ # Assertion helper used to assert validation failure. If a
19
+ # message is given, then the error message should match the
20
+ # expected error message.
21
+ def assert_failure(message=nil)
22
+ ex = assert_raises(Test::Unit::AssertionFailedError) { yield }
23
+ if message
24
+ case message
25
+ when Regexp
26
+ assert_match message, ex.message
27
+ when String
28
+ assert ex.message.index(message), "Error message '#{ex.message}' should contain '#{message}'"
29
+ end
30
+ end
31
+ ex
32
+ end
33
+ end
34
+ end