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 70 def self.make_proxy_for(obj, container, name, safe_mode) name ||= "flexmock(#{obj.class.to_s})" if !obj.instance_variable_defined?("@flexmock_proxy") proxy_box = obj.instance_variable_set("@flexmock_proxy", ProxyBox.new) else proxy_box = obj.instance_variable_get("@flexmock_proxy") end if proxy_box.container != container if !proxy_box.empty? parent_proxy, _ = proxy_box.proxy parent_mock = parent_proxy.mock end mock = FlexMock.new(name, container, parent: parent_mock) proxy = PartialMockProxy.new(obj, mock, safe_mode, parent: parent_proxy) proxy_box.push(proxy, container) end proxy_box.proxy end
Initialize a PartialMockProxy
object.
# File lib/flexmock/partial_mock.rb, line 103 def initialize(obj, mock, safe_mode, parent: nil) @obj = obj @mock = mock @proxy_definition_module = nil @parent = parent @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
Public Instance Methods
# File lib/flexmock/partial_mock.rb, line 238 def add_mock_method(method_name) proxy_module_eval do define_method(method_name) { |*args, **kw, &block| proxy = __flexmock_proxy or fail "Missing FlexMock proxy " + "(for method_name=#{method_name.inspect}, self=\#{self})" proxy.send(method_name, *args, **kw, &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
Whether the given method’s original definition has been stored
# File lib/flexmock/partial_mock.rb, line 169 def find_original_method(m) it = if m.respond_to?(:to_str) || m.respond_to?(:to_sym) @obj.method(m) else m end while it && (it.owner != @proxy_definition_module) it = it.super_method end return unless it while it && it.owner.kind_of?(ProxyDefinitionModule) it = it.super_method end it rescue NameError => e raise unless e.name == m end
Forward the based on request.
# File lib/flexmock/partial_mock.rb, line 404 def flexmock_based_on(*args) @mock.flexmock_based_on(*args) end
Forward to the mock
# File lib/flexmock/partial_mock.rb, line 389 def flexmock_calls @mock.flexmock_calls end
Forward to the mock’s container.
# File lib/flexmock/partial_mock.rb, line 379 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 395 def flexmock_container=(container) end
# File lib/flexmock/partial_mock.rb, line 213 def flexmock_define_expectation(location, *args, **kw) EXP_BUILDER.parse_should_args(self, args, kw) do |method_name| if !has_proxied_method?(method_name) define_proxy_method(method_name) end ex = @mock.flexmock_define_expectation(location, method_name) if FlexMock.partials_verify_signatures if (existing_method = find_original_method(method_name)) if flexmock_plain_new_method?(existing_method) # Look for the signature of `initialize` instead ex.with_signature_matching(@obj.instance_method(:initialize)) else ex.with_signature_matching(existing_method) end end end ex.mock = self ex end end
Forward the request for the expectation director to the mock.
# File lib/flexmock/partial_mock.rb, line 399 def flexmock_expectations_for(method_name) @mock.flexmock_expectations_for(method_name) end
# File lib/flexmock/partial_mock.rb, line 234 def flexmock_find_expectation(*args, **kw, &block) @mock.flexmock_find_expectation(*args, **kw, &block) end
Get the mock object for the partial mock.
# File lib/flexmock/partial_mock.rb, line 121 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 346 def flexmock_invoke_original(method, args, kw, block) if (original_method = find_original_method(method)) original_method.call(*args, **kw, &block) else @obj.__send__(:method_missing, method, *args, **kw, &block) end end
# File lib/flexmock/partial_mock.rb, line 209 def flexmock_plain_new_method?(m) m.name == :new && m.owner == Class end
Forward to the mock
# File lib/flexmock/partial_mock.rb, line 384 def flexmock_received?(*args, **kw) @mock.flexmock_received?(*args, **kw) end
Remove all traces of the mocking framework from the existing object.
# File lib/flexmock/partial_mock.rb, line 361 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.pop 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 356 def flexmock_verify @mock.flexmock_verify end
Whether the given method’s original definition has been stored
# File lib/flexmock/partial_mock.rb, line 199 def has_original_method?(m) find_original_method(m) end
Whether the given method is already being proxied
# File lib/flexmock/partial_mock.rb, line 204 def has_proxied_method?(m) @proxy_definition_module && @proxy_definition_module.method_defined?(m) end
Stubs the initialize method on a class
# File lib/flexmock/partial_mock.rb, line 289 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, **kw, &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, **kw, &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 317 def initialize_stub? !!@initialize_override end
# File lib/flexmock/partial_mock.rb, line 321 def initialize_stub_remove if initialize_stub? @initialize_expectation_blocks.clear @initialize_expectation_recorders.clear end end
Invoke the original of a mocked method
Usually called in a and_return statement
# File lib/flexmock/partial_mock.rb, line 164 def invoke_original(m, *args, **kw, &block) flexmock_invoke_original(m, args, kw, block) 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 267 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, **kw| create_new_mocked_object( allocate_method, args, kw, expectation_recorder, block) } end expectation_recorder end
Whether the given method’s original definition has been stored
# File lib/flexmock/partial_mock.rb, line 191 def original_method(m) unless (m = find_original_method(m)) raise ArgumentError, "no original method for #{m}" end m end
# File lib/flexmock/partial_mock.rb, line 129 def pop_flexmock_container @mock.pop_flexmock_container end
# File lib/flexmock/partial_mock.rb, line 125 def push_flexmock_container(container) @mock.push_flexmock_container(container) end
# File lib/flexmock/partial_mock.rb, line 157 def should_expect(*args) yield Recorder.new(self) 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 153 def should_receive(*args, **kw) flexmock_define_expectation(caller, *args, **kw) 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 335 def create_new_mocked_object(allocate_method, args, kw, recorder, block) new_obj = flexmock_invoke_original(allocate_method, args, kw, nil) 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 453 def define_proxy_method(method_name) if method_name =~ /=$/ proxy_module_eval do define_method(method_name) do |*args, **kw, &block| __flexmock_proxy.mock.__send__(method_name, *args, **kw, &block) end end else proxy_module_eval <<-EOD def #{method_name}(*args, **kw, &block) FlexMock.verify_mocking_allowed! __flexmock_proxy.mock.#{method_name}(*args, **kw, &block) end EOD end end
Have we been detached from the existing object?
# File lib/flexmock/partial_mock.rb, line 471 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 446 def hide_existing_method(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 425 def proxy_module_eval(*args, **kw, &block) if !@proxy_definition_module obj = @obj @proxy_definition_module = m = ProxyDefinitionModule.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, **kw, &block) 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 417 def target_class_eval(*args, **kw, &block) target_singleton_class.class_eval(*args, **kw, &block) end
The singleton class of the object.
# File lib/flexmock/partial_mock.rb, line 411 def target_singleton_class @obj.singleton_class end