class FlexMock::PartialMockProxy
PartialMockProxy is used to mate the
mock framework to an existing object. The object is “enhanced” with a
reference to a mock object (stored in @flexmock_proxy
). When
the should_receive
method is sent to the proxy, it overrides
the existing object's method by creating singleton method that forwards
to the mock. When testing is complete, PartialMockProxy will erase the mocking
infrastructure from the object being mocked (e.g. remove instance variables
and mock singleton methods).
Constants
- MOCK_METHODS
The following methods are added to partial mocks so that they can act like a mock.
- ProxyBox
Attributes
Public Class Methods
Make a partial mock proxy and install it on the target obj
.
# File lib/flexmock/partial_mock.rb, line 34 def self.make_proxy_for(obj, container, name, safe_mode) name ||= "flexmock(#{obj.class.to_s})" if ! proxy_defined_on?(obj) mock = FlexMock.new(name, container) proxy = PartialMockProxy.new(obj, mock, safe_mode) if obj.instance_variable_defined?("@flexmock_proxy") obj.instance_variable_get("@flexmock_proxy").proxy = proxy else obj.instance_variable_set("@flexmock_proxy", ProxyBox.new(proxy)) end end obj.instance_variable_get("@flexmock_proxy").proxy end
Initialize a PartialMockProxy object.
# File lib/flexmock/partial_mock.rb, line 65 def initialize(obj, mock, safe_mode) @obj = obj @mock = mock @method_definitions = {} @methods_proxied = [] @proxy_definition_module = nil @initialize_override = nil unless safe_mode add_mock_method(:should_receive) MOCK_METHODS.each do |sym| unless @obj.respond_to?(sym) add_mock_method(sym) end end end end
Is there a mock proxy defined on the domain object?
# File lib/flexmock/partial_mock.rb, line 49 def self.proxy_defined_on?(obj) obj.instance_variable_defined?("@flexmock_proxy") && obj.instance_variable_get("@flexmock_proxy").proxy end
Public Instance Methods
# File lib/flexmock/partial_mock.rb, line 126 def add_mock_method(method_name) stow_existing_definition(method_name) proxy_module_eval do define_method(method_name) { |*args, &block| proxy = __flexmock_proxy or fail "Missing FlexMock proxy " + "(for method_name=#{method_name.inspect}, self=\#{self})" proxy.send(method_name, *args, &block) } end end
#any_instance is present for backwards compatibility with version 0.5.0. @deprecated
# File lib/flexmock/deprecated_methods.rb, line 54 def any_instance(&block) $stderr.puts "any_instance is deprecated, use new_instances instead." new_instances(&block) end
Forward the based on request.
# File lib/flexmock/partial_mock.rb, line 297 def flexmock_based_on(*args) @mock.flexmock_based_on(*args) end
Forward to the mock
# File lib/flexmock/partial_mock.rb, line 282 def flexmock_calls @mock.flexmock_calls end
Forward to the mock's container.
# File lib/flexmock/partial_mock.rb, line 272 def flexmock_container @mock.flexmock_container end
Set the proxy's mock container. This set value is ignored because the proxy always uses the container of its mock.
# File lib/flexmock/partial_mock.rb, line 288 def flexmock_container=(container) end
# File lib/flexmock/partial_mock.rb, line 111 def flexmock_define_expectation(location, *args) EXP_BUILDER.parse_should_args(self, args) do |method_name| unless @methods_proxied.include?(method_name) hide_existing_method(method_name) end ex = @mock.flexmock_define_expectation(location, method_name) ex.mock = self ex end end
Forward the request for the expectation director to the mock.
# File lib/flexmock/partial_mock.rb, line 292 def flexmock_expectations_for(method_name) @mock.flexmock_expectations_for(method_name) end
# File lib/flexmock/partial_mock.rb, line 122 def flexmock_find_expectation(*args) @mock.flexmock_find_expectation(*args) end
Get the mock object for the partial mock.
# File lib/flexmock/partial_mock.rb, line 83 def flexmock_get @mock end
Invoke the original definition of method on the object supported by the stub.
# File lib/flexmock/partial_mock.rb, line 235 def flexmock_invoke_original(method, args) if original_method = @method_definitions[method] if Proc === args.last block = args.last args = args[0..-2] end original_method.bind(@obj).call(*args, &block) else @obj.__send__(:method_missing, method, *args, &block) end end
Forward to the mock
# File lib/flexmock/partial_mock.rb, line 277 def flexmock_received?(*args) @mock.flexmock_received?(*args) end
Remove all traces of the mocking framework from the existing object.
# File lib/flexmock/partial_mock.rb, line 254 def flexmock_teardown if ! detached? initialize_stub_remove proxy_module_eval do methods = instance_methods(false).to_a methods.each do |m| remove_method m end end if @obj.instance_variable_defined?(:@flexmock_proxy) && (box = @obj.instance_variable_get(:@flexmock_proxy)) box.proxy = nil end @obj = nil end end
Verify that the mock has been properly called. After verification, detach the mocking infrastructure from the existing object.
# File lib/flexmock/partial_mock.rb, line 249 def flexmock_verify @mock.flexmock_verify end
Stubs the initialize method on a class
# File lib/flexmock/partial_mock.rb, line 178 def initialize_stub(recorder, expectations_block) if !@initialize_override expectation_blocks = @initialize_expectation_blocks = Array.new expectation_recorders = @initialize_expectation_recorders = Array.new @initialize_override = Module.new do define_method :initialize do |*args, &block| if self.class.respond_to?(:__flexmock_proxy) && (mock = self.class.__flexmock_proxy) container = mock.flexmock_container mock = container.flexmock(self) expectation_blocks.each do |b| b.call(mock) end expectation_recorders.each do |r| r.apply(mock) end end super(*args, &block) end end override = @initialize_override @obj.class_eval { prepend override } end if expectations_block @initialize_expectation_blocks << expectations_block end @initialize_expectation_recorders << recorder end
# File lib/flexmock/partial_mock.rb, line 206 def initialize_stub? !!@initialize_override end
# File lib/flexmock/partial_mock.rb, line 210 def initialize_stub_remove if initialize_stub? @initialize_expectation_blocks.clear @initialize_expectation_recorders.clear end end
#new_instances is a short cut method for overriding the behavior of any new instances created via a mocked class object.
By default, #new_instances will mock the behaviour of the :new method. If you wish to mock a different set of class methods, just pass a list of symbols to as arguments. (previous versions also mocked :allocate by default. If you need :allocate to be mocked, just request it explicitly).
For example, to stub only objects created by :make (and not :new), use:
flexmock(ClassName).new_instances(:make).should_receive(...)
# File lib/flexmock/partial_mock.rb, line 156 def new_instances(*allocators, &block) fail ArgumentError, "new_instances requires a Class to stub" unless Class === @obj location = caller allocators = [:initialize] if allocators.empty? expectation_recorder = ExpectationRecorder.new if allocators.delete(:initialize) initialize_stub(expectation_recorder, block) end allocators.each do |allocate_method| flexmock_define_expectation(location, allocate_method).and_return { |*args| create_new_mocked_object( allocate_method, args, expectation_recorder, block) } end expectation_recorder end
Declare that the partial mock should receive a message with the given name.
If more than one method name is given, then the mock object should expect
to receive all the listed melthods. If a hash of method name/value pairs
is given, then the each method will return the associated result. Any
expectations applied to the result of should_receive
will be
applied to all the methods defined in the argument list.
An expectation object for the method name is returned as the result of this method. Further expectation constraints can be added by chaining to the result.
See Expectation for a list of declarators that can be used.
# File lib/flexmock/partial_mock.rb, line 107 def should_receive(*args) flexmock_define_expectation(caller, *args) end
Private Instance Methods
Create a new mocked object.
The mocked object is created using the following steps: (1) Allocate with the original allocation method (and args) (2) Pass to the block for custom configuration. (3) Apply any recorded expecations
# File lib/flexmock/partial_mock.rb, line 224 def create_new_mocked_object(allocate_method, args, recorder, block) new_obj = flexmock_invoke_original(allocate_method, args) mock = flexmock_container.flexmock(new_obj) block.call(mock) unless block.nil? recorder.apply(mock) new_obj end
Define a proxy method that forwards to our mock object. The proxy method is defined as a singleton method on the object being mocked.
# File lib/flexmock/partial_mock.rb, line 354 def define_proxy_method(method_name) if method_name =~ /=$/ proxy_module_eval do define_method(method_name) do |*args, &block| __flexmock_proxy.mock.__send__(method_name, *args, &block) end end else proxy_module_eval <<-EOD def #{method_name}(*args, &block) FlexMock.verify_mocking_allowed! __flexmock_proxy.mock.#{method_name}(*args, &block) end EOD end end
Have we been detached from the existing object?
# File lib/flexmock/partial_mock.rb, line 372 def detached? @obj.nil? end
Hide the existing method definition with a singleton defintion that proxies to our mock object. If the current definition is a singleton, we need to record the definition and remove it before creating our own singleton method. If the current definition is not a singleton, all we need to do is override it with our own singleton.
# File lib/flexmock/partial_mock.rb, line 336 def hide_existing_method(method_name) stow_existing_definition(method_name) define_proxy_method(method_name) end
Evaluate a block into the module we use to define the proxy methods
# File lib/flexmock/partial_mock.rb, line 315 def proxy_module_eval(*args, &block) if !@proxy_definition_module obj = @obj @proxy_definition_module = m = Module.new do define_method("__flexmock_proxy") do if box = obj.instance_variable_get(:@flexmock_proxy) box.proxy end end end target_class_eval { prepend m } end @proxy_definition_module.class_eval(*args, &block) end
Stow the existing method definition so that it can be recovered later.
# File lib/flexmock/partial_mock.rb, line 343 def stow_existing_definition(method_name) if !@methods_proxied.include?(method_name) @method_definitions[method_name] = target_class_eval { instance_method(method_name) } @methods_proxied << method_name end rescue NameError end
Evaluate a block (or string) in the context of the singleton class of the target partial object.
# File lib/flexmock/partial_mock.rb, line 310 def target_class_eval(*args, &block) target_singleton_class.class_eval(*args, &block) end
The singleton class of the object.
# File lib/flexmock/partial_mock.rb, line 304 def target_singleton_class @obj.singleton_class end