Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Stubbing of methods re-declared with different visibilty. Fixes #109.

Given a method which is re-declared with a different visibility lower
down the ancestor chain, and given we stub the "version" of the method
that is lower down the ancestor chain, we should ensure it is restored
to its original visibility when it is "unstubbed".

This was highlighted in #109 where the FileUtils module in Ruby 2.0
declares the module-instance methods as private and then re-declares
the module-class methods as public [1]. Even though this bug was
highlighted in Ruby 2.0, it can happen in Ruby 1.8 or 1.9 if methods
are declared in the same way.

We've added a new acceptance test to demonstrate the bug and fixed it.
The fix is to store and restore the method visibility whether or not
we remove the stubbed method.

Note that in Ruby 1.8.7 there is a warning about re-defining the method,
but we can't see an easy way around this at the moment.

[1]
ruby/ruby@ace4630#L1R161
  • Loading branch information...
commit cf91bded89fe2172f2f82146aaacaa520f564b0e 1 parent 3f4bece
@chrisroos-and-floehopper chrisroos-and-floehopper authored
Showing with 27 additions and 7 deletions.
  1. +10 −7 lib/mocha/class_method.rb
  2. +17 −0 test/acceptance/unstubbing_test.rb
View
17 lib/mocha/class_method.rb
@@ -7,7 +7,8 @@ class ClassMethod
attr_reader :stubbee, :method
def initialize(stubbee, method)
- @stubbee, @original_method = stubbee, nil
+ @stubbee = stubbee
+ @original_method, @original_visibility = nil, nil
@method = RUBY_VERSION < '1.9' ? method.to_s : method.to_sym
end
@@ -37,13 +38,13 @@ def hide_original_method
if method_exists?(method)
begin
@original_method = stubbee._method(method)
+ @original_visibility = :public
+ if stubbee.__metaclass__.protected_instance_methods.include?(method)
+ @original_visibility = :protected
+ elsif stubbee.__metaclass__.private_instance_methods.include?(method)
+ @original_visibility = :private
+ end
if @original_method && @original_method.owner == stubbee.__metaclass__
- @original_visibility = :public
- if stubbee.__metaclass__.protected_instance_methods.include?(method)
- @original_visibility = :protected
- elsif stubbee.__metaclass__.private_instance_methods.include?(method)
- @original_visibility = :private
- end
stubbee.__metaclass__.send(:remove_method, method)
end
rescue NameError
@@ -74,6 +75,8 @@ def restore_original_method
else
stubbee.__metaclass__.send(:define_method, method, @original_method)
end
+ end
+ if @original_visibility
stubbee.__metaclass__.send(@original_visibility, method)
end
end
View
17 test/acceptance/unstubbing_test.rb
@@ -50,6 +50,23 @@ def self.my_module_method; :original_return_value; end
assert_passed(test_result)
end
+ def test_unstubbing_a_module_method_defined_like_fileutils_in_ruby_2_0_should_restore_original_behaviour
+ mod = Module.new do
+ def my_module_method; :original_return_value; end
+ private :my_module_method
+ extend self
+ class << self
+ public :my_module_method
+ end
+ end
+ test_result = run_as_test do
+ mod.stubs(:my_module_method).returns(:new_return_value)
+ mod.unstub(:my_module_method)
+ assert_equal :original_return_value, mod.my_module_method
+ end
+ assert_passed(test_result)
+ end
+
def test_unstubbing_an_any_instance_method_should_restore_original_behaviour
klass = Class.new do
def my_instance_method; :original_return_value; end
Please sign in to comment.
Something went wrong with that request. Please try again.