Skip to content

Commit efd3f6a

Browse files
committed
Merge pull request #297 from alexdean/any_instance_of_prepend_fix
[rspec-mocks] prevent `any_instance` from stubbing private/protected methods defined in prepended modules
1 parent 27fe84e commit efd3f6a

2 files changed

Lines changed: 137 additions & 28 deletions

File tree

rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,9 @@ def mark_invoked!(method_name)
283283
if Support::RubyFeatures.module_prepends_supported?
284284
def allow_no_prepended_module_definition_of(method_name)
285285
prepended_modules = RSpec::Mocks::Proxy.prepended_modules_of(@klass)
286-
problem_mod = prepended_modules.find { |mod| mod.method_defined?(method_name) }
286+
problem_mod = prepended_modules.find do |mod|
287+
MethodReference.method_defined_at_any_visibility?(mod, method_name)
288+
end
287289
return unless problem_mod
288290

289291
AnyInstance.error_generator.raise_not_supported_with_prepend_error(method_name, problem_mod)

rspec-mocks/spec/rspec/mocks/any_instance_spec.rb

Lines changed: 134 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -178,34 +178,92 @@ def private_method; :private_method_return_value; end
178178
end
179179

180180
context "when the class has a prepended module", :skip => !Support::RubyFeatures.module_prepends_supported? do
181-
it 'allows stubbing a method that is not defined on the prepended module' do
182-
klass.class_eval { prepend Module.new { def other; end } }
183-
allow_any_instance_of(klass).to receive(:foo).and_return(45)
181+
context 'with a method not defined on the prepended module' do
182+
before do
183+
klass.class_eval { prepend Module.new { def other; end } }
184+
end
184185

185-
expect(klass.new.foo).to eq(45)
186+
it 'allows stubbing the method' do
187+
allow_any_instance_of(klass).to receive(:foo).and_return(45)
188+
189+
expect(klass.new.foo).to eq(45)
190+
end
191+
192+
it 'allows stubbing a chain starting with the method' do
193+
allow_any_instance_of(klass).to receive_message_chain(:foo, :bar).and_return(45)
194+
195+
expect(klass.new.foo.bar).to eq(45)
196+
end
186197
end
187198

188-
it 'prevents stubbing a method that is defined on the prepended module' do
189-
klass.class_eval { prepend Module.new { def foo; end } }
199+
context 'with a public method defined on the prepended module' do
200+
before do
201+
klass.class_eval { prepend Module.new { def foo; end } }
202+
end
190203

191-
expect {
192-
allow_any_instance_of(klass).to receive(:foo).and_return(45)
193-
}.to fail_with(/prepended module/)
204+
it 'prevents stubbing the method' do
205+
expect {
206+
allow_any_instance_of(klass).to receive(:foo).and_return(45)
207+
}.to fail_with(/prepended module/)
208+
end
209+
210+
it 'prevents stubbing a chain starting with the method' do
211+
expect {
212+
allow_any_instance_of(klass).to receive_message_chain(:foo, :bar).and_return(45)
213+
}.to fail_with(/prepended module/)
214+
end
194215
end
195216

196-
it 'allows stubbing a chain starting with a method that is not defined on the prepended module' do
197-
klass.class_eval { prepend Module.new { def other; end } }
198-
allow_any_instance_of(klass).to receive_message_chain(:foo, :bar).and_return(45)
217+
context 'with a protected method defined on the prepended module' do
218+
before do
219+
klass.class_eval do
220+
prepend Module.new {
221+
protected
222+
223+
def protected_method
224+
:prepended_protected_method_return_value
225+
end
226+
}
227+
end
228+
end
229+
230+
it 'prevents stubbing the method' do
231+
expect {
232+
allow_any_instance_of(klass).to receive(:protected_method).and_return(45)
233+
}.to fail_with(/prepended module/)
234+
end
199235

200-
expect(klass.new.foo.bar).to eq(45)
236+
it 'prevents stubbing a chain starting with the method' do
237+
expect {
238+
allow_any_instance_of(klass).to receive_message_chain(:protected_method, :bar).and_return(45)
239+
}.to fail_with(/prepended module/)
240+
end
201241
end
202242

203-
it 'prevents stubbing a chain starting with a method that is defined on the prepended module' do
204-
klass.class_eval { prepend Module.new { def foo; end } }
243+
context 'with a private method defined on the prepended module' do
244+
before do
245+
klass.class_eval do
246+
prepend Module.new {
247+
private
205248

206-
expect {
207-
allow_any_instance_of(klass).to receive_message_chain(:foo, :bar).and_return(45)
208-
}.to fail_with(/prepended module/)
249+
def private_method
250+
:prepended_private_method_return_value
251+
end
252+
}
253+
end
254+
end
255+
256+
it 'prevents stubbing the method' do
257+
expect {
258+
allow_any_instance_of(klass).to receive(:private_method).and_return(45)
259+
}.to fail_with(/prepended module/)
260+
end
261+
262+
it 'prevents stubbing a chain starting with the method' do
263+
expect {
264+
allow_any_instance_of(klass).to receive_message_chain(:private_method, :bar).and_return(45)
265+
}.to fail_with(/prepended module/)
266+
end
209267
end
210268
end
211269

@@ -592,19 +650,68 @@ def inspect
592650
end
593651

594652
context "when the class has a prepended module", :skip => !Support::RubyFeatures.module_prepends_supported? do
595-
it 'allows mocking a method that is not defined on the prepended module' do
596-
klass.class_eval { prepend Module.new { def other; end } }
597-
expect_any_instance_of(klass).to receive(:foo).and_return(45)
653+
context 'with a method not defined on the prepended module' do
654+
before do
655+
klass.class_eval { prepend Module.new { def other; end } }
656+
end
598657

599-
expect(klass.new.foo).to eq(45)
658+
it 'allows mocking the method' do
659+
expect_any_instance_of(klass).to receive(:foo).and_return(45)
660+
661+
expect(klass.new.foo).to eq(45)
662+
end
600663
end
601664

602-
it 'prevents mocking a method that is defined on the prepended module' do
603-
klass.class_eval { prepend Module.new { def foo; end } }
665+
context 'with a public method defined on the prepended module' do
666+
before do
667+
klass.class_eval { prepend Module.new { def foo; end } }
668+
end
604669

605-
expect {
606-
expect_any_instance_of(klass).to receive(:foo).and_return(45)
607-
}.to fail_with(/prepended module/)
670+
it 'prevents mocking the method' do
671+
expect {
672+
expect_any_instance_of(klass).to receive(:foo).and_return(45)
673+
}.to fail_with(/prepended module/)
674+
end
675+
end
676+
677+
context 'with a protected method defined on the prepended module' do
678+
before do
679+
klass.class_eval do
680+
prepend Module.new {
681+
protected
682+
683+
def protected_method
684+
:prepended_protected_method_return_value
685+
end
686+
}
687+
end
688+
end
689+
690+
it 'prevents mocking the method' do
691+
expect {
692+
expect_any_instance_of(klass).to receive(:protected_method).and_return(45)
693+
}.to fail_with(/prepended module/)
694+
end
695+
end
696+
697+
context 'with a private method defined on the prepended module' do
698+
before do
699+
klass.class_eval do
700+
prepend Module.new {
701+
private
702+
703+
def private_method
704+
:prepended_private_method_return_value
705+
end
706+
}
707+
end
708+
end
709+
710+
it 'prevents mocking the method' do
711+
expect {
712+
expect_any_instance_of(klass).to receive(:private_method).and_return(45)
713+
}.to fail_with(/prepended module/)
714+
end
608715
end
609716
end
610717

0 commit comments

Comments
 (0)