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