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.
Attributes
Public Class Methods
Make a partial mock proxy and install it on the target obj
.
# File lib/flexmock/partial_mock.rb, line 32 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) obj.instance_variable_set("@flexmock_proxy", proxy) end obj.instance_variable_get("@flexmock_proxy") end
Initialize a PartialMockProxy object.
# File lib/flexmock/partial_mock.rb, line 59 def initialize(obj, mock, safe_mode) @obj = obj @mock = mock @method_definitions = {} @methods_proxied = [] 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 43 def self.proxy_defined_on?(obj) obj.instance_variable_defined?("@flexmock_proxy") && obj.instance_variable_get("@flexmock_proxy") end
Public Instance Methods
# File lib/flexmock/partial_mock.rb, line 119 def add_mock_method(method_name) stow_existing_definition(method_name) target_class_eval do define_method(method_name) { |*args, &block| proxy = instance_variable_get("@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 232 def flexmock_based_on(*args) @mock.flexmock_based_on(*args) end
Forward to the mock
# File lib/flexmock/partial_mock.rb, line 217 def flexmock_calls @mock.flexmock_calls end
Forward to the mock's container.
# File lib/flexmock/partial_mock.rb, line 207 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 223 def flexmock_container=(container) end
# File lib/flexmock/partial_mock.rb, line 104 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 227 def flexmock_expectations_for(method_name) @mock.flexmock_expectations_for(method_name) end
# File lib/flexmock/partial_mock.rb, line 115 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 75 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 183 def flexmock_invoke_original(method, args) method_proc = @method_definitions[method] method_proc.call(*args) end
Forward to the mock
# File lib/flexmock/partial_mock.rb, line 212 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 195 def flexmock_teardown if ! detached? @methods_proxied.each do |method_name| remove_current_method(method_name) restore_original_definition(method_name) end @obj.instance_variable_set("@flexmock_proxy", nil) @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 190 def flexmock_verify @mock.flexmock_verify 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 149 def new_instances(*allocators, &block) fail ArgumentError, "new_instances requires a Class to stub" unless Class === @obj location = caller.first allocators = [:new] if allocators.empty? expectation_recorder = ExpectationRecorder.new allocators.each do |allocate_method| check_allocate_method(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 99 def should_receive(*args) location = caller.first flexmock_define_expectation(location, *args) end
Private Instance Methods
# File lib/flexmock/partial_mock.rb, line 238 def check_allocate_method(allocate_method) if allocate_method == :allocate && RUBY_VERSION >= "1.9" fail UsageError, "Cannot mock the allocation method using new_instances in Ruby 1.9" end end
Create an alias for the existing method_name
. Returns the new
alias name. If the aliasing process fails (because the method doesn't
really exist, then return nil.
# File lib/flexmock/partial_mock.rb, line 299 def create_alias_for_existing_method(method_name) new_alias = new_name(method_name) unless @obj.respond_to?(new_alias) safe_alias_method(new_alias, method_name) end new_alias end
Create a method definition that invokes the original behavior via the alias.
# File lib/flexmock/partial_mock.rb, line 284 def create_aliased_definition(my_object, new_alias) Proc.new { |*args| block = nil if Proc === args.last block = args.last args = args[0...-1] end my_object.send(new_alias, *args, &block) } end
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 172 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 324 def define_proxy_method(method_name) if method_name.to_s =~ /=$/ eval_line = __LINE__ + 1 target_class_eval %Q{ def #{method_name}(*args, &block) instance_variable_get('@flexmock_proxy'). mock.__send__(:#{method_name}, *args, &block) end }, __FILE__, eval_line else eval_line = __LINE__ + 1 target_class_eval %Q{ def #{method_name}(*args, &block) instance_variable_get('@flexmock_proxy'). mock.#{method_name}(*args, &block) end }, __FILE__, eval_line _ = true # make rcov recognize the above eval is covered end end
Have we been detached from the existing object?
# File lib/flexmock/partial_mock.rb, line 369 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 266 def hide_existing_method(method_name) stow_existing_definition(method_name) define_proxy_method(method_name) end
Generate a name to be used to alias the original behavior.
# File lib/flexmock/partial_mock.rb, line 374 def new_name(old_name) "flexmock_original_behavior_for_#{old_name}" end
Remove the current method if it is a singleton method of the object being mocked.
# File lib/flexmock/partial_mock.rb, line 364 def remove_current_method(method_name) target_class_eval { remove_method(method_name) } end
Restore the original singleton defintion for method_name that was saved earlier.
# File lib/flexmock/partial_mock.rb, line 347 def restore_original_definition(method_name) begin method_def = @method_definitions[method_name] if method_def the_alias = new_name(method_name) target_class_eval do alias_method(method_name, the_alias) end end rescue NameError => _ # Alias attempt failed nil end end
Create an alias for the existing method named method_name
. It
is possible that method_name
is implemented via a
meta-programming, so we provide for the case that the method_name does not
exist.
# File lib/flexmock/partial_mock.rb, line 311 def safe_alias_method(new_alias, method_name) target_class_eval do begin alias_method(new_alias, method_name) rescue NameError nil end end end
# File lib/flexmock/partial_mock.rb, line 256 def singleton?(method_name) @obj.flexmock_singleton_defined?(method_name) end
Stow the existing method definition so that it can be recovered later.
# File lib/flexmock/partial_mock.rb, line 273 def stow_existing_definition(method_name) @methods_proxied << method_name new_alias = create_alias_for_existing_method(method_name) if new_alias @method_definitions[method_name] = create_aliased_definition(@obj, new_alias) end remove_current_method(method_name) if singleton?(method_name) 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 252 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 246 def target_singleton_class class << @obj; self; end end