Skip to content

Commit

Permalink
Merge pull request #541 from rspec/issue-529
Browse files Browse the repository at this point in the history
Correct stub undefined parent modules all the way down when stubbing a nested constant.
  • Loading branch information
xaviershay committed Jan 26, 2014
2 parents 8ec4f5b + 1827ee4 commit fd6af3c
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 12 deletions.
2 changes: 2 additions & 0 deletions Changelog.md
Expand Up @@ -43,6 +43,8 @@ Bug Fixes:
* Fix regression in `stub_chain`/`receive_message_chain` that caused
it to raise an `ArgumentError` when passing args to the stubbed
methods. (Sam Phippen)
* Correct stub of undefined parent modules all the way down when stubbing a
nested constant. (Xavier Shay)

Enhancements:

Expand Down
31 changes: 19 additions & 12 deletions lib/rspec/mocks/mutate_const.rb
Expand Up @@ -322,19 +322,15 @@ def verify_constants_to_transfer!
# @api private
class UndefinedConstantSetter < BaseMutator
def mutate
remaining_parts = @context_parts.dup
@deepest_defined_const = @context_parts.inject(Object) do |klass, name|
break klass unless const_defined_on?(klass, name)
remaining_parts.shift
get_const_defined_on(klass, name)
end

context = remaining_parts.inject(@deepest_defined_const) do |klass, name|
klass.const_set(name, Module.new)
@parent = @context_parts.inject(Object) do |klass, name|
if const_defined_on?(klass, name)
get_const_defined_on(klass, name)
else
ConstantMutator.stub(name_for(klass, name), Module.new)
end
end

@const_to_remove = remaining_parts.first || @const_name
context.const_set(@const_name, @mutated_value)
@parent.const_set(@const_name, @mutated_value)
end

def to_constant
Expand All @@ -346,7 +342,18 @@ def to_constant
end

def reset
@deepest_defined_const.__send__(:remove_const, @const_to_remove)
@parent.__send__(:remove_const, @const_name)
end

private

def name_for(parent, name)
root = if parent == Object
''
else
parent.name
end
root + '::' + name
end
end

Expand Down
12 changes: 12 additions & 0 deletions spec/rspec/mocks/mutate_const_spec.rb
Expand Up @@ -451,6 +451,18 @@ def change_const_value_to(value)
it("returns nil for the original value") { expect(const.original_value).to be_nil }
end

context 'for a previously undefined parent of a stubbed constant' do
before { stub_const("TestClass::UndefinedModule::Undefined", :other) }
let(:const) { Constant.original("TestClass::UndefinedModule") }

it("exposes its name") { expect(const.name).to eq("TestClass::UndefinedModule") }
it("indicates it was not previously defined") { expect(const).not_to be_previously_defined }
it("indicates it has been mutated") { expect(const).to be_mutated }
it("indicates it has been stubbed") { expect(const).to be_stubbed }
it("indicates it has not been hidden") { expect(const).not_to be_hidden }
it("returns nil for the original value") { expect(const.original_value).to be_nil }
end

context 'for a previously undefined unstubbed constant' do
let(:const) { Constant.original("TestClass::Undefined") }

Expand Down
7 changes: 7 additions & 0 deletions spec/rspec/mocks/verifying_double_spec.rb
Expand Up @@ -84,6 +84,13 @@ module Mocks
expect(o.undefined_instance_method(:arg)).to eq(true)
end

it 'handles classes that are materialized after mocking' do
stub_const "A::B", Object.new
o = instance_double "A", :undefined_instance_method => true

expect(o.undefined_instance_method).to eq(true)
end

context 'for null objects' do
let(:o) { instance_double('NonLoadedClass').as_null_object }

Expand Down

0 comments on commit fd6af3c

Please sign in to comment.