flexmock 0.6.4 → 0.7.0

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