Skip to content

Commit

Permalink
Update rspec-fire to work with port of stub_const in rspec-mocks.
Browse files Browse the repository at this point in the history
rspec/rspec-mocks#146

Note that this removes support for rspec 2.0...2.10
(and rspec 2.11 isn't out yet), but there's no reason to
maintain stub_const logic here now that it has been ported
to rspec-mocks, and if you're updating to the latest rspec-fire
than we assume you're probably doing the same with rspec.
  • Loading branch information
myronmarston committed Jun 11, 2012
1 parent ef2662f commit f68425b
Show file tree
Hide file tree
Showing 3 changed files with 8 additions and 429 deletions.
24 changes: 1 addition & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,25 +144,6 @@ collaborators (a technique that can sometimes be cumbersome).
This will probably become the default behaviour once we figure out a better
name for it.

### Stubbing Constants

The constant stubbing logic used when doubling class constants can be
used for any constant.

class MapReduceRunner
ITEMS_PER_BATCH = 1000
end

describe MapReduceRunner, "when it has too many items for one batch" do
it "breaks the items up into smaller batches" do
# the test would be really slow if we had to make more than 1000 items,
# so let's change the threshold for this one test.
stub_const("MapReduceRunner::ITEMS_PER_BATCH", 10)

MapReduceRunner.run_with(twenty_items)
end
end

### Transferring nested constants to doubled constants

When you use `fire_replaced_class_double` to replace a class or module
Expand All @@ -186,9 +167,6 @@ to deal with this:
# ...or give it a list of constants to transfer
fire_class_double("MyCoolGem").as_replaced_constant(:transfer_nested_constants => [:Widget])

# You can also use this when using #stub_const directly
stub_const("MyCoolGem", :transfer_nested_constants => true)

### Doubling class methods

Particularly handy for `ActiveRecord` finders. Use `fire_class_double`. If you
Expand All @@ -207,7 +185,7 @@ refactor or use a non-isolated test.
Compatibility
-------------

Only RSpec 2 is supported. Tested on all the rubies thanks to [Travis
Only RSpec 2.11+ is supported. Tested on all the rubies thanks to [Travis
CI][build-link].

[build-link]: http://travis-ci.org/xaviershay/rspec-fire
Expand Down
164 changes: 7 additions & 157 deletions lib/rspec/fire.rb
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ def stub!(method_name)
end

def with_doubled_class
ConstantStubber.find_original_value_for(@__doubled_class_name) do |value|
::RSpec::Fire.find_original_value_for(@__doubled_class_name) do |value|
yield value if value
return
end
Expand Down Expand Up @@ -221,7 +221,9 @@ def self.method_missing(name, *args)
end

def self.as_replaced_constant(options = {})
@__original_class = ConstantStubber.stub!(@__doubled_class_name, self, options)
RSpec::Mocks::ConstantStubber.stub(@__doubled_class_name, self, options)
@__original_class = RSpec::Mocks::Constant.original(@__doubled_class_name).original_value

extend AsReplacedConstant
self
end
Expand All @@ -247,161 +249,9 @@ def with_doubled_class
end
end

class ConstantStubber
extend RecursiveConstMethods

class DefinedConstantReplacer
include RecursiveConstMethods
attr_reader :original_value, :full_constant_name

def initialize(full_constant_name, stubbed_value, transfer_nested_constants)
@full_constant_name = full_constant_name
@stubbed_value = stubbed_value
@transfer_nested_constants = transfer_nested_constants
end

def stub!
context_parts = @full_constant_name.split('::')
@const_name = context_parts.pop
@context = recursive_const_get(context_parts.join('::'))
@original_value = @context.const_get(@const_name)

constants_to_transfer = verify_constants_to_transfer!

@context.send(:remove_const, @const_name)
@context.const_set(@const_name, @stubbed_value)

transfer_nested_constants(constants_to_transfer)
end

def rspec_reset
if recursive_const_get(@full_constant_name).equal?(@stubbed_value)
@context.send(:remove_const, @const_name)
@context.const_set(@const_name, @original_value)
end
end

def transfer_nested_constants(constants)
constants.each do |const|
@stubbed_value.const_set(const, original_value.const_get(const))
end
end

def verify_constants_to_transfer!
return [] unless @transfer_nested_constants

{ @original_value => "the original value", @stubbed_value => "the stubbed value" }.each do |value, description|
unless value.respond_to?(:constants)
raise ArgumentError,
"Cannot transfer nested constants for #{@full_constant_name} " +
"since #{description} is not a class or module and only classes " +
"and modules support nested constants."
end
end

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

if undefined_constants.any?
available_constants = @original_value.constants - @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 " +
"#{available_constants.join(' or ')}?"
end

@transfer_nested_constants
else
@original_value.constants
end
end
end

class UndefinedConstantSetter
include RecursiveConstMethods

attr_reader :full_constant_name

def initialize(full_constant_name, stubbed_value)
@full_constant_name = full_constant_name
@stubbed_value = stubbed_value
end

def original_value
# always nil
end

def stub!
context_parts = @full_constant_name.split('::')
const_name = context_parts.pop

remaining_parts = context_parts.dup
@deepest_defined_const = context_parts.inject(Object) do |klass, name|
break klass unless klass.const_defined?(name)
remaining_parts.shift
klass.const_get(name)
end

context = remaining_parts.inject(@deepest_defined_const) do |klass, name|
klass.const_set(name, Module.new)
end

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

def rspec_reset
if recursive_const_get(@full_constant_name).equal?(@stubbed_value)
@deepest_defined_const.send(:remove_const, @const_to_remove)
end
end
end

def self.stub!(constant_name, value, options = {})
stubber = if recursive_const_defined?(constant_name)
DefinedConstantReplacer.new(constant_name, value, options[:transfer_nested_constants])
else
UndefinedConstantSetter.new(constant_name, value)
end

stubbers << stubber

stubber.stub!
ensure_registered_with_rspec_mocks
stubber.original_value
end

def self.ensure_registered_with_rspec_mocks
return if @registered_with_rspec_mocks
::RSpec::Mocks.space.add(self)
@registered_with_rspec_mocks = true
end

def self.rspec_reset
@registered_with_rspec_mocks = false

# We use reverse order so that if the same constant
# was stubbed multiple times, the original value gets
# properly restored.
stubbers.reverse.each { |s| s.rspec_reset }

stubbers.clear
end

def self.stubbers
@stubbers ||= []
end

def self.find_original_value_for(constant_name)
stubber = stubbers.find { |s| s.full_constant_name == constant_name }
yield stubber.original_value if stubber
self
end
end

def stub_const(name, value, options = {})
ConstantStubber.stub!(name, value, options)
def self.find_original_value_for(constant_name)
const = ::RSpec::Mocks::Constant.original(constant_name)
yield const.original_value if const.stubbed?
end

def fire_double(*args)
Expand Down
Loading

0 comments on commit f68425b

Please sign in to comment.