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.
- data/CHANGES +8 -0
- data/README +116 -3
- data/Rakefile +5 -5
- data/TAGS +396 -282
- data/doc/releases/flexmock-0.7.0.rdoc +138 -0
- data/lib/flexmock/base.rb +2 -1
- data/lib/flexmock/core.rb +30 -29
- data/lib/flexmock/core_class_methods.rb +21 -30
- data/lib/flexmock/deprecated_methods.rb +62 -0
- data/lib/flexmock/expectation.rb +101 -21
- data/lib/flexmock/expectation_director.rb +10 -5
- data/lib/flexmock/mock_container.rb +144 -29
- data/lib/flexmock/ordering.rb +51 -0
- data/lib/flexmock/partial_mock.rb +23 -22
- data/test/asserts.rb +34 -0
- data/test/redirect_error.rb +16 -0
- data/test/rspec_integration/integration_spec.rb +6 -0
- data/test/test_demeter_mocking.rb +129 -0
- data/test/{test_mock.rb → test_deprecated_methods.rb} +64 -17
- data/test/test_example.rb +4 -3
- data/test/test_extended_should_receive.rb +1 -1
- data/test/test_flexmodel.rb +17 -1
- data/test/test_naming.rb +43 -8
- data/test/test_new_instances.rb +2 -22
- data/test/test_partial_mock.rb +19 -19
- data/test/test_record_mode.rb +27 -38
- data/test/test_should_receive.rb +199 -27
- metadata +11 -4
@@ -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 =
|
39
|
-
@expectations.find { |e| e.match_args(args) }
|
38
|
+
exp = find_expectation(*args)
|
40
39
|
FlexMock.check("no matching handler found for " +
|
41
|
-
|
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
|
57
|
+
def flexmock_verify
|
53
58
|
@expectations.each do |exp|
|
54
|
-
exp.
|
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.
|
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.
|
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 =
|
140
|
+
mock = ContainerHelper.make_partial_proxy(self, domain_obj, name, safe_mode)
|
138
141
|
result = domain_obj
|
139
142
|
elsif model_class
|
140
|
-
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
|
-
|
148
|
+
mock.should_receive(quick_defs)
|
146
149
|
yield(mock) if block_given?
|
147
150
|
flexmock_remember(mock)
|
148
|
-
|
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
|
-
|
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
|
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
|
229
|
+
def make_partial_proxy(container, obj, name, safe_mode)
|
172
230
|
name ||= "flexmock(#{obj.class.to_s})"
|
173
231
|
obj.instance_eval {
|
174
|
-
|
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
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
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
|
-
|
295
|
+
exp
|
185
296
|
end
|
186
297
|
|
187
|
-
#
|
188
|
-
def
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
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
|
-
|
196
|
-
|
197
|
-
|
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
|
-
|
28
|
-
|
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,
|
35
|
-
:
|
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
|
-
|
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 =
|
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
|
145
|
-
@mock.
|
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
|
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
|
-
#
|
161
|
-
def
|
162
|
-
@
|
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
|
data/test/asserts.rb
ADDED
@@ -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
|