Skip to content

Commit

Permalink
Fix transfer_nested_constants option of stub_const.
Browse files Browse the repository at this point in the history
My changes in deec990 caused it to blow up in the face of inherited constants.
  • Loading branch information
myronmarston committed Aug 14, 2012
1 parent e7bd234 commit 39ae1f3
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 11 deletions.
8 changes: 8 additions & 0 deletions Changelog.md
@@ -1,3 +1,11 @@
### dev
[full changelog](http://github.com/rspec/rspec-mocks/compare/v2.11.2...master)

Bug fixes

* Fix `:transfer_nested_constants` option of `stub_const` so that it
doesn't blow up when there are inherited constants. (Myron Marston)

### 2.11.2 / 2012-08-11
[full changelog](http://github.com/rspec/rspec-mocks/compare/v2.11.1...v2.11.2)

Expand Down
35 changes: 24 additions & 11 deletions lib/rspec/mocks/stub_const.rb
Expand Up @@ -10,17 +10,22 @@ module RecursiveConstMethods
# we need to conditionally define methods to ignore the top-level/inherited
# constants.
#
# Given `class A; end`:
# Given:
# class A; B = 1; end
# class C < A; end
#
# On 1.8:
# - A.const_get("Hash") # => ::Hash
# - A.const_defined?("Hash") # => false
# - Neither method accepts the extra `inherit` argument
# - C.const_get("Hash") # => ::Hash
# - C.const_defined?("Hash") # => false
# - C.constants # => ["A"]
# - None of these methods accept 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
# - C.const_get("Hash") # => ::Hash
# - C.const_defined?("Hash") # => true
# - C.const_get("Hash", false) # => raises NameError
# - C.const_defined?("Hash", false) # => false
# - C.constants # => [:A]
# - C.constants(false) #=> []
if Module.method(:const_defined?).arity == 1
def const_defined_on?(mod, const_name)
mod.const_defined?(const_name)
Expand All @@ -33,6 +38,10 @@ def get_const_defined_on(mod, const_name)

raise NameError, "uninitialized constant #{mod.name}::#{const_name}"
end

def constants_defined_on(mod)
mod.constants.select { |c| const_defined_on?(mod, c) }
end
else
def const_defined_on?(mod, const_name)
mod.const_defined?(const_name, false)
Expand All @@ -41,6 +50,10 @@ def const_defined_on?(mod, const_name)
def get_const_defined_on(mod, const_name)
mod.const_get(const_name, false)
end

def constants_defined_on(mod)
mod.constants(false)
end
end

def recursive_const_get(const_name)
Expand Down Expand Up @@ -217,10 +230,10 @@ def verify_constants_to_transfer!

if @transfer_nested_constants.is_a?(Array)
@transfer_nested_constants = @transfer_nested_constants.map(&:to_s) if RUBY_VERSION == '1.8.7'
undefined_constants = @transfer_nested_constants - @original_value.constants
undefined_constants = @transfer_nested_constants - constants_defined_on(@original_value)

if undefined_constants.any?
available_constants = @original_value.constants - @transfer_nested_constants
available_constants = constants_defined_on(@original_value) - @transfer_nested_constants
raise ArgumentError,
"Cannot transfer nested constant(s) #{undefined_constants.join(' and ')} " +
"for #{@full_constant_name} since they are not defined. Did you mean " +
Expand All @@ -229,7 +242,7 @@ def verify_constants_to_transfer!

@transfer_nested_constants
else
@original_value.constants
constants_defined_on(@original_value)
end
end
end
Expand Down
22 changes: 22 additions & 0 deletions spec/rspec/mocks/stub_const_spec.rb
Expand Up @@ -12,6 +12,10 @@ class NestedEvenMore
end
end

class TestSubClass < TestClass
P = :p
end

module RSpec
module Mocks
describe "Constant Stubbing" do
Expand Down Expand Up @@ -121,6 +125,24 @@ def change_const_value_to(value)
stub::Nested.should be(tc_nested)
end

it 'does not transfer nested constants that are inherited from a superclass' do
stub = Module.new
stub_const("TestSubClass", stub, :transfer_nested_constants => true)
stub::P.should eq(:p)
defined?(stub::M).should be_false
defined?(stub::N).should be_false
end

it 'raises an error when asked to transfer a nested inherited constant' do
original_tsc = TestSubClass

expect {
stub_const("TestSubClass", Module.new, :transfer_nested_constants => [:M])
}.to raise_error(ArgumentError)

TestSubClass.should be(original_tsc)
end

it 'allows nested constants to be selectively transferred to a stub module' do
stub = Module.new
stub_const("TestClass", stub, :transfer_nested_constants => [:M, :N])
Expand Down

0 comments on commit 39ae1f3

Please sign in to comment.