Given a class `TheClass`, `TheClass.any_instance` returns a `Recorder`, which records stubs and message expectations for later playback on instances of `TheClass`.
Further constraints are stored in instances of [Chain](Chain).
@see AnyInstance @see Chain
@private
@private
@private
# File lib/rspec/mocks/any_instance/recorder.rb, line 16 def initialize(klass) @message_chains = MessageChains.new @stubs = Hash.new { |hash,key| hash[key] = [] } @observed_methods = [] @played_methods = {} @klass = klass @expectation_set = false end
@private
# File lib/rspec/mocks/any_instance/recorder.rb, line 120 def already_observing?(method_name) @observed_methods.include?(method_name) || super_class_observing?(method_name) end
@private
# File lib/rspec/mocks/any_instance/recorder.rb, line 115 def build_alias_method_name(method_name) "__#{method_name}_without_any_instance__" end
@private
# File lib/rspec/mocks/any_instance/recorder.rb, line 47 def expect_chain(*method_names_and_optional_return_values, &block) @expectation_set = true normalize_chain(*method_names_and_optional_return_values) do |method_name, args| observe!(method_name) message_chains.add(method_name, ExpectChainChain.new(self, *args, &block)) end end
@private
# File lib/rspec/mocks/any_instance/recorder.rb, line 110 def instance_that_received(method_name) @played_methods[method_name] end
@private
# File lib/rspec/mocks/any_instance/recorder.rb, line 125 def notify_received_message(object, message, args, blk) has_expectation = false message_chains.each_unfulfilled_expectation_matching(message, *args) do |expectation| has_expectation = true expectation.expectation_fulfilled! end if has_expectation restore_method!(message) mark_invoked!(message) end end
@private
# File lib/rspec/mocks/any_instance/recorder.rb, line 102 def playback!(instance, method_name) RSpec::Mocks.space.ensure_registered(instance) message_chains.playback!(instance, method_name) @played_methods[method_name] = instance received_expected_message!(method_name) if message_chains.has_expectation?(method_name) end
The opposite of `should_receive`
@see Methods#should_not_receive
# File lib/rspec/mocks/any_instance/recorder.rb, line 69 def should_not_receive(method_name, &block) should_receive(method_name, &block).never end
Initializes the recording a message expectation to be played back against any instance of this object that invokes the submitted method.
@see Methods#should_receive
# File lib/rspec/mocks/any_instance/recorder.rb, line 60 def should_receive(method_name, &block) @expectation_set = true observe!(method_name) message_chains.add(method_name, PositiveExpectationChain.new(self, method_name, &block)) end
@private
# File lib/rspec/mocks/any_instance/recorder.rb, line 97 def stop_all_observation! @observed_methods.each {|method_name| restore_method!(method_name)} end
Initializes the recording a stub to be played back against any instance of this object that invokes the submitted method.
@see Methods#stub
# File lib/rspec/mocks/any_instance/recorder.rb, line 29 def stub(method_name, &block) observe!(method_name) message_chains.add(method_name, StubChain.new(self, method_name, &block)) end
Initializes the recording a stub chain to be played back against any instance of this object that invokes the method matching the first argument.
@see Methods#stub_chain
# File lib/rspec/mocks/any_instance/recorder.rb, line 39 def stub_chain(*method_names_and_optional_return_values, &block) normalize_chain(*method_names_and_optional_return_values) do |method_name, args| observe!(method_name) message_chains.add(method_name, StubChainChain.new(self, *args, &block)) end end
Removes any previously recorded stubs, stub_chains or message expectations that use `method_name`.
@see Methods#unstub
# File lib/rspec/mocks/any_instance/recorder.rb, line 77 def unstub(method_name) unless @observed_methods.include?(method_name.to_sym) raise RSpec::Mocks::MockExpectationError, "The method `#{method_name}` was not stubbed or was already unstubbed" end message_chains.remove_stub_chains_for!(method_name) stubs[method_name].clear stop_observing!(method_name) unless message_chains.has_expectation?(method_name) end
@api private
Used internally to verify that message expectations have been fulfilled.
# File lib/rspec/mocks/any_instance/recorder.rb, line 90 def verify if @expectation_set && !message_chains.all_expectations_fulfilled? raise RSpec::Mocks::MockExpectationError, "Exactly one instance should have received the following message(s) but didn't: #{message_chains.unfulfilled_expectations.sort.join(', ')}" end end
# File lib/rspec/mocks/any_instance/recorder.rb, line 141 def stop_observing!(method_name) restore_method!(method_name) @observed_methods.delete(method_name) super_class_observers_for(method_name).each do |ancestor| ::RSpec::Mocks.space. any_instance_recorder_for(ancestor).stop_observing!(method_name) end end
# File lib/rspec/mocks/any_instance/recorder.rb, line 247 def allow_no_prepended_module_definition_of(method_name) prepended_modules = @klass.ancestors.take_while { |mod| !(Class === mod) } problem_mod = prepended_modules.find { |mod| mod.method_defined?(method_name) } return unless problem_mod raise RSpec::Mocks::MockExpectationError, "Using `any_instance` to stub a method (#{method_name}) that has been " + "defined on a prepended module (#{problem_mod}) is not supported." end
# File lib/rspec/mocks/any_instance/recorder.rb, line 152 def ancestor_is_an_observer?(method_name) lambda do |ancestor| unless ancestor == @klass ::RSpec::Mocks.space. any_instance_recorder_for(ancestor).already_observing?(method_name) end end end
# File lib/rspec/mocks/any_instance/recorder.rb, line 205 def backup_method!(method_name) alias_method_name = build_alias_method_name(method_name) @klass.class_exec do alias_method alias_method_name, method_name end if public_protected_or_private_method_defined?(method_name) end
# File lib/rspec/mocks/any_instance/recorder.rb, line 236 def mark_invoked!(method_name) backup_method!(method_name) recorder = self @klass.__send__(:define_method, method_name) do |*args, &blk| invoked_instance = recorder.instance_that_received(method_name) inspect = "#<#{self.class}:#{object_id} #{instance_variables.map { |name| "#{name}=#{instance_variable_get name}" }.join(', ')}>" raise RSpec::Mocks::MockExpectationError, "The message '#{method_name}' was received by #{inspect} but has already been received by #{invoked_instance}" end end
# File lib/rspec/mocks/any_instance/recorder.rb, line 169 def normalize_chain(*args) args.shift.to_s.split('.').map {|s| s.to_sym}.reverse.each {|a| args.unshift a} yield args.first, args end
# File lib/rspec/mocks/any_instance/recorder.rb, line 216 def observe!(method_name) allow_no_prepended_module_definition_of(method_name) if RSpec::Mocks.configuration.verify_partial_doubles? unless public_protected_or_private_method_defined?(method_name) raise MockExpectationError, "#{@klass} does not implement ##{method_name}" end end stop_observing!(method_name) if already_observing?(method_name) @observed_methods << method_name backup_method!(method_name) recorder = self @klass.__send__(:define_method, method_name) do |*args, &blk| recorder.playback!(self, method_name) self.__send__(method_name, *args, &blk) end end
# File lib/rspec/mocks/any_instance/recorder.rb, line 212 def public_protected_or_private_method_defined?(method_name) MethodReference.method_defined_at_any_visibility?(@klass, method_name) end
# File lib/rspec/mocks/any_instance/recorder.rb, line 174 def received_expected_message!(method_name) message_chains.received_expected_message!(method_name) restore_method!(method_name) mark_invoked!(method_name) end
# File lib/rspec/mocks/any_instance/recorder.rb, line 199 def remove_dummy_method!(method_name) @klass.class_exec do remove_method method_name end end
# File lib/rspec/mocks/any_instance/recorder.rb, line 180 def restore_method!(method_name) if public_protected_or_private_method_defined?(build_alias_method_name(method_name)) restore_original_method!(method_name) else remove_dummy_method!(method_name) end end
# File lib/rspec/mocks/any_instance/recorder.rb, line 188 def restore_original_method!(method_name) if @klass.instance_method(method_name).owner == @klass alias_method_name = build_alias_method_name(method_name) @klass.class_exec do remove_method method_name alias_method method_name, alias_method_name remove_method alias_method_name end end end
# File lib/rspec/mocks/any_instance/recorder.rb, line 161 def super_class_observers_for(method_name) @klass.ancestors.select(&ancestor_is_an_observer?(method_name)) end
# File lib/rspec/mocks/any_instance/recorder.rb, line 165 def super_class_observing?(method_name) @klass.ancestors.any?(&ancestor_is_an_observer?(method_name)) end