diff --git a/lib/rspec/mocks/any_instance/expectation_chain.rb b/lib/rspec/mocks/any_instance/expectation_chain.rb index 699a2153b..97818eed2 100644 --- a/lib/rspec/mocks/any_instance/expectation_chain.rb +++ b/lib/rspec/mocks/any_instance/expectation_chain.rb @@ -25,7 +25,12 @@ class PositiveExpectationChain < ExpectationChain def create_message_expectation_on(instance) proxy = ::RSpec::Mocks.proxy_for(instance) expected_from = IGNORED_BACKTRACE_LINE - proxy.add_message_expectation(expected_from, *@expectation_args, &@expectation_block) + me = proxy.add_message_expectation(expected_from, *@expectation_args, &@expectation_block) + if RSpec::Mocks.configuration.yield_instance_from_any_instance_implementation_blocks + me.and_yield_receiver_to_implementation + end + + me end def invocation_order diff --git a/lib/rspec/mocks/any_instance/stub_chain.rb b/lib/rspec/mocks/any_instance/stub_chain.rb index 9b1712a70..ef25cfcb2 100644 --- a/lib/rspec/mocks/any_instance/stub_chain.rb +++ b/lib/rspec/mocks/any_instance/stub_chain.rb @@ -14,7 +14,13 @@ def expectation_fulfilled? def create_message_expectation_on(instance) proxy = ::RSpec::Mocks.proxy_for(instance) expected_from = IGNORED_BACKTRACE_LINE - proxy.add_stub(expected_from, *@expectation_args, &@expectation_block) + stub = proxy.add_stub(expected_from, *@expectation_args, &@expectation_block) + + if RSpec::Mocks.configuration.yield_instance_from_any_instance_implementation_blocks + stub.and_yield_receiver_to_implementation + end + + stub end def invocation_order diff --git a/lib/rspec/mocks/configuration.rb b/lib/rspec/mocks/configuration.rb index 4e5cd8cc4..0ba9b820b 100644 --- a/lib/rspec/mocks/configuration.rb +++ b/lib/rspec/mocks/configuration.rb @@ -23,6 +23,14 @@ def add_stub_and_should_receive_to(*modules) end end + def yield_instance_from_any_instance_implementation_blocks + @yield_instance_from_any_instance_implementation_blocks ||= false + end + + def yield_instance_from_any_instance_implementation_blocks=(arg) + @yield_instance_from_any_instance_implementation_blocks = arg + end + def syntax=(values) if Array(values).include?(:expect) Syntax.enable_expect diff --git a/lib/rspec/mocks/message_expectation.rb b/lib/rspec/mocks/message_expectation.rb index b6b9a939e..c3b80ac97 100644 --- a/lib/rspec/mocks/message_expectation.rb +++ b/lib/rspec/mocks/message_expectation.rb @@ -5,6 +5,8 @@ class MessageExpectation # @private attr_accessor :error_generator, :implementation attr_reader :message + attr_reader :orig_object + attr_reader :yield_receiver_to_implementation attr_writer :expected_received_count, :expected_from, :argument_list_matcher protected :expected_received_count=, :expected_from=, :error_generator, :error_generator=, :implementation= @@ -15,6 +17,7 @@ def initialize(error_generator, expectation_ordering, expected_from, method_doub @error_generator.opts = opts @expected_from = expected_from @method_double = method_double + @orig_object = @method_double.object @message = @method_double.method_name @actual_received_count = 0 @expected_received_count = expected_received_count @@ -24,6 +27,8 @@ def initialize(error_generator, expectation_ordering, expected_from, method_doub @args_to_yield = [] @failed_fast = nil @eval_context = nil + @yield_receiver_to_implementation = false + @is_any_instance_expectation = opts[:is_any_instance_expectation] @implementation = Implementation.new self.inner_implementation_action = implementation_block @@ -89,6 +94,11 @@ def and_return(*values, &implementation) end end + def and_yield_receiver_to_implementation + @yield_receiver_to_implementation = true + self + end + # Tells the object to delegate to the original unmodified method # when it receives the message. # diff --git a/lib/rspec/mocks/proxy.rb b/lib/rspec/mocks/proxy.rb index 5dc63b40f..da5660635 100644 --- a/lib/rspec/mocks/proxy.rb +++ b/lib/rspec/mocks/proxy.rb @@ -139,8 +139,14 @@ def message_received(message, *args, &block) if expectation = find_almost_matching_expectation(message, *args) expectation.advise(*args) unless expectation.expected_messages_received? end + if stub.yield_receiver_to_implementation + args.unshift(stub.orig_object) + end stub.invoke(nil, *args, &block) elsif expectation + if expectation.yield_receiver_to_implementation + args.unshift(expectation.orig_object) + end expectation.invoke(stub, *args, &block) elsif expectation = find_almost_matching_expectation(message, *args) expectation.advise(*args) if null_object? unless expectation.expected_messages_received? diff --git a/spec/rspec/mocks/any_instance_spec.rb b/spec/rspec/mocks/any_instance_spec.rb index 555357b63..5b7e8ca32 100644 --- a/spec/rspec/mocks/any_instance_spec.rb +++ b/spec/rspec/mocks/any_instance_spec.rb @@ -830,6 +830,67 @@ def foo; end end end + context "passing the receiver to the implementation block" do + context "when configured to pass the instance" do + include_context 'with isolated configuration' + before(:each) do + RSpec::Mocks.configuration.yield_instance_from_any_instance_implementation_blocks = true + end + + describe "an any instance stub" do + it "passes the instance as the first arg of the implementation block" do + instance = klass.new + + expect { |b| + klass.any_instance.should_receive(:bees).with(:sup, &b) + instance.bees(:sup) + }.to yield_with_args(instance, :sup) + end + end + + describe "an any instance expectation" do + it "doesn't effect with" do + instance = klass.new + klass.any_instance.should_receive(:bees).with(:sup) + instance.bees(:sup) + end + + it "passes the instance as the first arg of the implementation block" do + instance = klass.new + + expect { |b| + klass.any_instance.should_receive(:bees).with(:sup, &b) + instance.bees(:sup) + }.to yield_with_args(instance, :sup) + end + end + end + + context "when configured not to pass the instance" do + include_context 'with isolated configuration' + before(:each) do + RSpec::Mocks.configuration.yield_instance_from_any_instance_implementation_blocks = false + end + + describe "an any instance stub" do + it "does not pass the instance to the implementation block" do + instance = klass.new + + expect { |b| + klass.any_instance.should_receive(:bees).with(:sup, &b) + instance.bees(:sup) + }.to yield_with_args(:sup) + end + + it "does not cause with to fail when the instance is passed" do + instance = klass.new + klass.any_instance.should_receive(:bees).with(:faces) + instance.bees(:faces) + end + end + end + end + context 'when used in conjunction with a `dup`' do it "doesn't cause an infinite loop" do pending "This intermittently fails on JRuby" if RUBY_PLATFORM == 'java' diff --git a/spec/rspec/mocks/with_isolated_configuration_spec.rb b/spec/rspec/mocks/with_isolated_configuration_spec.rb new file mode 100644 index 000000000..0ddf046fc --- /dev/null +++ b/spec/rspec/mocks/with_isolated_configuration_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' +require 'pry' + +module RSpec + module Mocks + describe 'the with isolated configuration shared example group' do + @@c = describe '' do + include_context 'with isolated configuration' + end + it 'resets the configuration' do + @@c.before.first.block.call + RSpec::Mocks.configuration.instance_eval do + def this_method_wont_be_here + end + end + + @@c.after.last.block.call + expect(RSpec::Mocks.configuration.respond_to? :this_method_wont_be_here).to be false + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d160f68a6..3768bb797 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -68,3 +68,15 @@ def reset(object) end end + +shared_context "with isolated configuration" do + orig_configuration = nil + before do + orig_configuration = RSpec::Mocks.configuration + RSpec::Mocks.instance_variable_set(:@configuration, RSpec::Mocks::Configuration.new) + end + + after do + RSpec::Mocks.instance_variable_set(:@configuration, orig_configuration) + end +end