From a22c214cb5cac97a0bf15087d5c3d57ee73ab38c Mon Sep 17 00:00:00 2001 From: Jon Rowe Date: Wed, 2 Oct 2024 22:10:58 +0100 Subject: [PATCH 1/3] Mark any_instance proxy methods as private if they were private previously --- lib/rspec/mocks/any_instance/recorder.rb | 2 ++ spec/rspec/mocks/any_instance_spec.rb | 39 ++++++++++++++++++------ 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/lib/rspec/mocks/any_instance/recorder.rb b/lib/rspec/mocks/any_instance/recorder.rb index 4fa5ec66b..0eb8245b9 100644 --- a/lib/rspec/mocks/any_instance/recorder.rb +++ b/lib/rspec/mocks/any_instance/recorder.rb @@ -259,10 +259,12 @@ def observe!(method_name) @observed_methods << method_name backup_method!(method_name) recorder = self + method_was_private = @klass.private_method_defined?(method_name) @klass.__send__(:define_method, method_name) do |*args, &blk| recorder.playback!(self, method_name) __send__(method_name, *args, &blk) end + @klass.__send__(:private, method_name) if method_was_private @klass.__send__(:ruby2_keywords, method_name) if @klass.respond_to?(:ruby2_keywords, true) end diff --git a/spec/rspec/mocks/any_instance_spec.rb b/spec/rspec/mocks/any_instance_spec.rb index 06a62f586..024b42e90 100644 --- a/spec/rspec/mocks/any_instance_spec.rb +++ b/spec/rspec/mocks/any_instance_spec.rb @@ -1074,22 +1074,42 @@ def foo; end end context "private methods" do - before :each do - allow_any_instance_of(klass).to receive(:private_method).and_return(:something) + before(:example) { allow_any_instance_of(klass).to receive(:private_method).and_return(:something) } - verify_all + let(:object) { klass.new } + + it "maintains the method in the list of private_methods" do + expect { + verify_all + }.to_not change { object.private_methods.include?(:private_method) }.from(true) end - it "cleans up the backed up method" do - expect(klass.method_defined?(:__existing_method_without_any_instance__)).to be_falsey + it "maintains the methods exclusion from the list of public_methods" do + expect { + verify_all + }.to_not change { object.public_methods.include?(:private_method) }.from(false) end - it "restores a stubbed private method after the spec is run" do + it "maintains the methods visibility" do + expect { klass.new.private_method }.to raise_error(NoMethodError) + expect(klass.new.send(:private_method)).to eq(:something) expect(klass.private_method_defined?(:private_method)).to be_truthy end - it "ensures that the restored method behaves as it originally did" do - expect(klass.new.send(:private_method)).to eq(:private_method_return_value) + context "after the spec has run" do + before(:example) { verify_all } + + it "cleans up the backed up method" do + expect(klass.method_defined?(:__existing_method_without_any_instance__)).to be_falsey + end + + it "restores a stubbed private method after the spec is run" do + expect(klass.private_method_defined?(:private_method)).to be_truthy + end + + it "ensures that the restored method behaves as it originally did" do + expect(klass.new.send(:private_method)).to eq(:private_method_return_value) + end end end end @@ -1098,7 +1118,7 @@ def foo; end context "private methods" do before :each do expect_any_instance_of(klass).to receive(:private_method).and_return(:something) - klass.new.private_method + klass.new.send(:private_method) verify_all end @@ -1109,6 +1129,7 @@ def foo; end it "restores a stubbed private method after the spec is run" do expect(klass.private_method_defined?(:private_method)).to be_truthy + expect(klass.new.private_methods).to include :private_method end it "ensures that the restored method behaves as it originally did" do From 10d57e0359349210672ffa910634fbbeb04ea730 Mon Sep 17 00:00:00 2001 From: Jon Rowe Date: Wed, 2 Oct 2024 22:31:21 +0100 Subject: [PATCH 2/3] Fix specs for 1.8.7 --- spec/rspec/mocks/any_instance_spec.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/spec/rspec/mocks/any_instance_spec.rb b/spec/rspec/mocks/any_instance_spec.rb index 024b42e90..7b8a4ace1 100644 --- a/spec/rspec/mocks/any_instance_spec.rb +++ b/spec/rspec/mocks/any_instance_spec.rb @@ -1078,16 +1078,17 @@ def foo; end let(:object) { klass.new } + # The map(&:to_sym) is for legacy Ruby compatability and can be dropped in RSpec 4 it "maintains the method in the list of private_methods" do expect { verify_all - }.to_not change { object.private_methods.include?(:private_method) }.from(true) + }.to_not change { object.private_methods.map(&:to_sym).include?(:private_method) }.from(true) end it "maintains the methods exclusion from the list of public_methods" do expect { verify_all - }.to_not change { object.public_methods.include?(:private_method) }.from(false) + }.to_not change { object.public_methods.map(&:to_sym).include?(:private_method) }.from(false) end it "maintains the methods visibility" do @@ -1129,7 +1130,7 @@ def foo; end it "restores a stubbed private method after the spec is run" do expect(klass.private_method_defined?(:private_method)).to be_truthy - expect(klass.new.private_methods).to include :private_method + expect(klass.new.private_methods.map(&:to_sym)).to include :private_method end it "ensures that the restored method behaves as it originally did" do From b9a7c04b2917f1fa11f0866f096bf616353609cb Mon Sep 17 00:00:00 2001 From: Jon Rowe Date: Thu, 10 Oct 2024 21:12:17 +0100 Subject: [PATCH 3/3] Changelog for #1596 --- Changelog.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Changelog.md b/Changelog.md index 13570a1af..52e264dbe 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,12 @@ ### Development [Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.13.2...main) +Bug Fixes: + +* When stubbing methods using the `expect_any_instance_of` or `allow_any_instance_of` + ensure the stubbed method has the same visibility as the real method. + (Jon Rowe, #1596) + ### 3.13.2 / 2024-10-02 [Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.13.1...v3.13.2)