Permalink
Browse files

Fix use of const_get/const_defined? to handle undefined consts like F…

…oo::Hash.

When you name a nested constant that ends in a name that matches a top-level constant (such as "Foo::Hash"), rspec-fire was verifying the presence of a stubbed method on ::Hash if "Foo::Hash" was not defined. 1.9's const_get/const_defined? accepts a flag argument to have it ignore inherited/top-level constants, but 1.8 doesn't accept this argument, so we have to conditionally define methods to handle this properly.
  • Loading branch information...
1 parent 45cf4d6 commit b5fc940c49b8547754ed7732355fc519e4229406 @myronmarston myronmarston committed Aug 11, 2012
Showing with 48 additions and 3 deletions.
  1. +42 −3 lib/rspec/fire.rb
  2. +6 −0 spec/fire_double_spec.rb
View
@@ -76,14 +76,53 @@ def arity_description
end
module RecursiveConstMethods
+ # We only want to consider constants that are defined directly on a
+ # particular module, and not include top-level/inherited constants.
+ # Unfortunately, the constant API changed between 1.8 and 1.9, so
+ # we need to conditionally define methods to ignore the top-level/inherited
+ # constants.
+ #
+ # Given `class A; end`:
+ #
+ # On 1.8:
+ # - A.const_get("Hash") # => ::Hash
+ # - A.const_defined?("Hash") # => false
+ # - Neither method accepts the extra `inherit` argument
+ # On 1.9:
+ # - A.const_get("Hash") # => ::Hash
+ # - A.const_defined?("Hash") # => true
+ # - A.const_get("Hash", false) # => raises NameError
+ # - A.const_defined?("Hash", false) # => false
+ if Module.method(:const_defined?).arity == 1
+ def const_defined_on?(mod, const_name)
+ mod.const_defined?(const_name)
+ end
+
+ def get_const_defined_on(mod, const_name)
+ if const_defined_on?(mod, const_name)
+ return mod.const_get(const_name)
+ end
+
+ raise NameError, "uninitialized constant #{mod.name}::#{const_name}"
+ end
+ else
+ def const_defined_on?(mod, const_name)
+ mod.const_defined?(const_name, false)
+ end
+
+ def get_const_defined_on(mod, const_name)
+ mod.const_get(const_name, false)
+ end
+ end
+
def recursive_const_get name
- name.split('::').inject(Object) {|klass,name| klass.const_get name }
+ name.split('::').inject(Object) {|klass,name| get_const_defined_on(klass, name) }
end
def recursive_const_defined? name
!!name.split('::').inject(Object) {|klass,name|
- if klass && klass.const_defined?(name)
- klass.const_get name
+ if klass && const_defined_on?(klass, name)
+ get_const_defined_on(klass, name)
end
}
end
View
@@ -193,6 +193,12 @@ def fail_for_arguments(expected, actual)
double = fire_double("TestObject", :defined_method => 17)
double.defined_method.should eq(17)
end
+
+ it 'does not prevent stubbing methods on an unloaded nested constant with a name that matches a top-level constant' do
+ double = fire_double("TestObject::Hash")
+ double.stub(:foo).and_return("bar")
+ double.foo.should eq("bar")
+ end
end
describe '#fire_class_double' do

0 comments on commit b5fc940

Please sign in to comment.