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
Original file line number Original file line Diff line number Diff line change
@@ -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 ### 2.11.2 / 2012-08-11
[full changelog](http://github.com/rspec/rspec-mocks/compare/v2.11.1...v2.11.2) [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
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -10,17 +10,22 @@ module RecursiveConstMethods
# we need to conditionally define methods to ignore the top-level/inherited # we need to conditionally define methods to ignore the top-level/inherited
# constants. # constants.
# #
# Given `class A; end`: # Given:
# class A; B = 1; end
# class C < A; end
# #
# On 1.8: # On 1.8:
# - A.const_get("Hash") # => ::Hash # - C.const_get("Hash") # => ::Hash
# - A.const_defined?("Hash") # => false # - C.const_defined?("Hash") # => false
# - Neither method accepts the extra `inherit` argument # - C.constants # => ["A"]
# - None of these methods accept the extra `inherit` argument
# On 1.9: # On 1.9:
# - A.const_get("Hash") # => ::Hash # - C.const_get("Hash") # => ::Hash
# - A.const_defined?("Hash") # => true # - C.const_defined?("Hash") # => true
# - A.const_get("Hash", false) # => raises NameError # - C.const_get("Hash", false) # => raises NameError
# - A.const_defined?("Hash", false) # => false # - C.const_defined?("Hash", false) # => false
# - C.constants # => [:A]
# - C.constants(false) #=> []
if Module.method(:const_defined?).arity == 1 if Module.method(:const_defined?).arity == 1
def const_defined_on?(mod, const_name) def const_defined_on?(mod, const_name)
mod.const_defined?(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}" raise NameError, "uninitialized constant #{mod.name}::#{const_name}"
end end

def constants_defined_on(mod)
mod.constants.select { |c| const_defined_on?(mod, c) }
end
else else
def const_defined_on?(mod, const_name) def const_defined_on?(mod, const_name)
mod.const_defined?(const_name, false) 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) def get_const_defined_on(mod, const_name)
mod.const_get(const_name, false) mod.const_get(const_name, false)
end end

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


def recursive_const_get(const_name) 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) if @transfer_nested_constants.is_a?(Array)
@transfer_nested_constants = @transfer_nested_constants.map(&:to_s) if RUBY_VERSION == '1.8.7' @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? if undefined_constants.any?
available_constants = @original_value.constants - @transfer_nested_constants available_constants = constants_defined_on(@original_value) - @transfer_nested_constants
raise ArgumentError, raise ArgumentError,
"Cannot transfer nested constant(s) #{undefined_constants.join(' and ')} " + "Cannot transfer nested constant(s) #{undefined_constants.join(' and ')} " +
"for #{@full_constant_name} since they are not defined. Did you mean " + "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 @transfer_nested_constants
else else
@original_value.constants constants_defined_on(@original_value)
end end
end end
end end
Expand Down
22 changes: 22 additions & 0 deletions spec/rspec/mocks/stub_const_spec.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ class NestedEvenMore
end end
end end


class TestSubClass < TestClass
P = :p
end

module RSpec module RSpec
module Mocks module Mocks
describe "Constant Stubbing" do describe "Constant Stubbing" do
Expand Down Expand Up @@ -121,6 +125,24 @@ def change_const_value_to(value)
stub::Nested.should be(tc_nested) stub::Nested.should be(tc_nested)
end 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 it 'allows nested constants to be selectively transferred to a stub module' do
stub = Module.new stub = Module.new
stub_const("TestClass", stub, :transfer_nested_constants => [:M, :N]) stub_const("TestClass", stub, :transfer_nested_constants => [:M, :N])
Expand Down

0 comments on commit 39ae1f3

Please sign in to comment.