4
# Copyright 2003, 2004, 2005, 2006, 2007 by Jim Weirich (jim@weirichhouse.org).
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.
12
require 'flexmock/noop'
13
require 'flexmock/argument_types'
14
require 'flexmock/ordering'
18
# ######################################################################
19
# Mock container methods
21
# Include this module in to get integration with FlexMock. When this module
22
# is included, mocks may be created with a simple call to the +flexmock+
23
# method. Mocks created with via the method call will automatically be
24
# verified in the teardown of the test case.
29
# Do the flexmock specific teardown stuff. If you need finer control,
30
# you can use either +flexmock_verify+ or +flexmock_close+.
32
flexmock_verify if passed?
37
# Perform verification on all mocks in the container.
39
@flexmock_created_mocks ||= []
40
@flexmock_created_mocks.each do |m|
45
# Close all the mock objects in the container. Closing a mock object
46
# restores any original behavior that was displaced by the mock.
48
@flexmock_created_mocks ||= []
49
@flexmock_created_mocks.each do |m|
52
@flexmock_created_mocks = []
55
# Create a mocking object in the FlexMock framework. The +flexmock+
56
# method has a number of options available, depending on just what kind of
57
# mocking object your require. Mocks created via +flexmock+ will be
58
# automatically verify during the teardown phase of your test framework.
61
# flexmock() { |mock| ... }
62
# flexmock(name) { |mock| ... }
63
# flexmock(expect_hash) { |mock| ... }
64
# flexmock(name, expect_hash) { |mock| ... }
65
# flexmock(real_object) { |mock| ... }
66
# flexmock(real_object, name) { |mock| ... }
67
# flexmock(real_object, name, expect_hash) { |mock| ... }
68
# flexmock(:base, string, name, expect_hash) { |mock| ... }
70
# <b>Note:</b> A plain flexmock() call without a block will return the
71
# mock object (the object that interprets <tt>should_receive</tt> and its
72
# brethern). A flexmock() call that _includes_ a block will return the
73
# domain objects (the object that will interpret domain messages) since
74
# the mock will be passed to the block for configuration. With regular
75
# mocks, this distinction is unimportant because the mock object and the
76
# domain object are the same object. However, with partial mocks, the
77
# mock object is separation from the domain object. Keep that distinciton
81
# Name of the mock object. If no name is given, "unknown" is used for
82
# full mocks and "flexmock(<em>real_object</em>)" is used for partial
86
# Hash table of method names and values. Each method/value pair is
87
# used to setup a simple expectation so that if the mock object
88
# receives a message matching an entry in the table, it returns
89
# the associated value. No argument our call count constraints are
90
# added. Using an expect_hash is identical to calling:
92
# mock.should_receive(method_name).and_return(value)
94
# for each of the method/value pairs in the hash.
97
# If a real object is given, then a partial mock is constructed
98
# using the real_object as a base. Partial mocks (formally referred
99
# to as stubs) behave as a mock object when an expectation is matched,
100
# and otherwise will behave like the original object. This is useful
101
# when you want to use a real object for testing, but need to mock out
102
# just one or two methods.
105
# Forces the following argument to be used as the base of a
106
# partial mock object. This explicit tag is only needed if you
107
# want to use a string or a symbol as the mock base (string and
108
# symbols would normally be interpretted as the mock name).
111
# If a block is given, then the mock object is passed to the block and
112
# expectations may be configured within the block. When a block is given
113
# for a partial mock, flexmock will return the domain object rather than
125
safe_mode = (args.shift == :safe)
126
domain_obj = args.shift
129
model_class = args.shift
131
name = args.shift.to_s
133
quick_defs = args.shift
135
domain_obj = args.shift
138
raise UsageError, "a block is required in safe mode" if safe_mode && ! block_given?
141
mock = ContainerHelper.make_partial_proxy(self, domain_obj, name, safe_mode)
144
id = ContainerHelper.next_id
145
result = mock = FlexMock.new("#{model_class}_#{id}", self)
147
result = mock = FlexMock.new(name || "unknown", self)
149
mock.should_receive(quick_defs)
150
yield(mock) if block_given?
151
flexmock_remember(mock)
152
ContainerHelper.add_model_methods(mock, model_class, id) if model_class
155
alias flexstub flexmock
157
# Remember the mock object / stub in the mock container.
158
def flexmock_remember(mocking_object)
159
@flexmock_created_mocks ||= []
160
@flexmock_created_mocks << mocking_object
161
mocking_object.flexmock_container = self
166
# #################################################################
167
# Helper methods for mock containers. MockContainer is a module
168
# that is designed to be mixed into other classes, particularly
169
# testing framework test cases. Since we don't want to pollute the
170
# method namespace of the class that mixes in MockContainer, a
171
# number of MockContainer methods were moved into ContainerHelper to
172
# to isoloate the names.
174
class MockContainerHelper
175
include FlexMock::ArgumentTypes
177
# Return the next id for mocked models.
179
@id_counter ||= 10000
184
# parse_should_args(args) { |symbol| ... }
186
# This method provides common handling for the various should_receive
187
# argument lists. It sorts out the differences between symbols, arrays and
188
# hashes, and identifies the method names specified by each. As each
189
# method name is identified, create a mock expectation for it using the
191
def parse_should_args(mock, args, &block) # :nodoc:
192
result = CompositeExpectation.new
197
exp = build_demeter_chain(mock, k, &block).and_return(v)
201
result.add(build_demeter_chain(mock, arg, &block))
207
# Automatically add mocks for some common methods in ActiveRecord
209
def add_model_methods(mock, model_class, id)
210
container = mock.flexmock_container
212
mock_errors = container.flexmock("errors")
213
mock_errors.should_receive(:count).and_return(0).by_default
214
mock_errors.should_receive(:full_messages).and_return([]).by_default
216
mock.should_receive(:id).and_return(id).by_default
217
mock.should_receive(:to_params).and_return(id.to_s).by_default
218
mock.should_receive(:new_record?).and_return(false).by_default
219
mock.should_receive(:class).and_return(model_class).by_default
220
mock.should_receive(:errors).and_return(mock_errors).by_default
222
# HACK: Ruby 1.9 needs the following lambda so that model_class
223
# is correctly bound below.
225
mock.should_receive(:is_a?).with(any).and_return { |other|
228
mock.should_receive(:instance_of?).with(any).and_return { |other|
231
mock.should_receive(:kind_of?).with(any).and_return { |other|
232
model_class.ancestors.include?(other)
236
# Create a PartialMockProxy for the given object. Use +name+ as
237
# the name of the mock object.
238
def make_partial_proxy(container, obj, name, safe_mode)
239
name ||= "flexmock(#{obj.class.to_s})"
241
mock = FlexMock.new(name, container)
242
@flexmock_proxy ||= PartialMockProxy.new(obj, mock, safe_mode)
244
obj.instance_variable_get("@flexmock_proxy")
249
# Build the chain of mocks for demeter style mocking.
251
# Warning: Nasty code ahead.
253
# This method builds a chain of mocks to support demeter style
254
# mocking. Given a mock chain of "first.second.third.last", we
255
# must build a chain of mock methods that return the next mock in
256
# the chain. The expectation for the last method of the chain is
257
# returned as the result of the method.
259
# Things to consider:
261
# (1) The expectation for the "first" method must be created by
262
# the proper mechanism, which is supplied by the block parameter
263
# "block". In other words, first expectation is created by
264
# calling the block. (This allows us to create expectations on
265
# both pure mocks and partial mocks, with the block handling the
268
# (2) Although the first mock is arbitrary, the remaining mocks in
269
# the chain will always be pure mocks created specifically for
272
# (3) The expectations for all methods but the last in the chain
273
# will be setup to expect no parameters and to return the next
276
# (4) It could very well be the case that several demeter chains
277
# will be defined on a single mock object, and those chains could
278
# share some of the same methods (e.g. "mock.one.two.read" and
279
# "mock.one.two.write" both share the methods "one" and "two").
280
# It is important that the shared methods return the same mocks in
283
def build_demeter_chain(mock, arg, &block)
284
container = mock.flexmock_container
285
names = arg.to_s.split('.')
286
check_method_names(names)
288
next_exp = lambda { |n| block.call(n) }
290
method_name = names.shift.to_sym
291
exp = mock.flexmock_find_expectation(method_name)
292
need_new_exp = exp.nil? || names.empty?
293
exp = next_exp.call(method_name) if need_new_exp
294
break if names.empty?
296
mock = container.flexmock("demeter_#{method_name}")
297
exp.with_no_args.and_return(mock)
299
mock = exp._return_value([])
301
check_proper_mock(mock, method_name)
302
next_exp = lambda { |n| mock.should_receive(n) }
307
# Check that the given mock is a real FlexMock mock.
308
def check_proper_mock(mock, method_name)
309
unless mock.kind_of?(FlexMock)
310
fail FlexMock::UsageError,
311
"Conflicting mock declaration for '#{method_name}' in demeter style mock"
315
METHOD_NAME_RE = /^([A-Za-z_][A-Za-z0-9_]*[=!?]?|\[\]=?||\*\*|<<|>>|<=>|[<>=]=|=~|===|[-+]@|[-+\*\/%&^|<>~])$/
317
# Check that all the names in the list are valid method names.
318
def check_method_names(names)
320
fail FlexMock::UsageError, "Ill-formed method name '#{name}'" if
321
name !~ METHOD_NAME_RE
326
ContainerHelper = MockContainerHelper.new