RSpec::Support.require_rspec_support'recursive_const_methods'moduleRSpecmoduleMocks# Provides information about constants that may (or may not)# have been mutated by rspec-mocks.classConstantextendSupport::RecursiveConstMethods# @api privatedefinitialize(name)@name=name@previously_defined=false@stubbed=false@hidden=false@valid_name=trueyieldselfifblock_given?end# @return [String] The fully qualified name of the constant.attr_reader:name# @return [Object, nil] The original value (e.g. before it# was mutated by rspec-mocks) of the constant, or# nil if the constant was not previously defined.attr_accessor:original_value# @privateattr_writer:previously_defined,:stubbed,:hidden,:valid_name# @return [Boolean] Whether or not the constant was defined# before the current example.defpreviously_defined?@previously_definedend# @return [Boolean] Whether or not rspec-mocks has mutated# (stubbed or hidden) this constant.defmutated?@stubbed||@hiddenend# @return [Boolean] Whether or not rspec-mocks has stubbed# this constant.defstubbed?@stubbedend# @return [Boolean] Whether or not rspec-mocks has hidden# this constant.defhidden?@hiddenend# @return [Boolean] Whether or not the provided constant name# is a valid Ruby constant name.defvalid_name?@valid_nameend# The default `to_s` isn't very useful, so a custom version is provided.defto_s"#<#{self.class.name}#{name}>"endaliasinspectto_s# @privatedefself.unmutated(name)previously_defined=recursive_const_defined?(name)rescueNameErrornew(name)do|c|c.valid_name=falseendelsenew(name)do|const|const.previously_defined=previously_definedconst.original_value=recursive_const_get(name)ifpreviously_definedendend# Queries rspec-mocks to find out information about the named constant.## @param [String] name the name of the constant# @return [Constant] an object contaning information about the named# constant.defself.original(name)mutator=::RSpec::Mocks.space.constant_mutator_for(name)mutator?mutator.to_constant:unmutated(name)endend# Provides a means to stub constants.classConstantMutatorextendSupport::RecursiveConstMethods# Stubs a constant.## @param (see ExampleMethods#stub_const)# @option (see ExampleMethods#stub_const)# @return (see ExampleMethods#stub_const)## @see ExampleMethods#stub_const# @note It's recommended that you use `stub_const` in your# examples. This is an alternate public API that is provided# so you can stub constants in other contexts (e.g. helper# classes).defself.stub(constant_name,value,options={})mutator=ifrecursive_const_defined?(constant_name,&raise_on_invalid_const)DefinedConstantReplacerelseUndefinedConstantSetterendmutate(mutator.new(constant_name,value,options[:transfer_nested_constants]))valueend# Hides a constant.## @param (see ExampleMethods#hide_const)## @see ExampleMethods#hide_const# @note It's recommended that you use `hide_const` in your# examples. This is an alternate public API that is provided# so you can hide constants in other contexts (e.g. helper# classes).defself.hide(constant_name)mutate(ConstantHider.new(constant_name,nil,{}))nilend# Contains common functionality used by all of the constant mutators.## @privateclassBaseMutatorincludeSupport::RecursiveConstMethodsattr_reader:original_value,:full_constant_namedefinitialize(full_constant_name,mutated_value,transfer_nested_constants)@full_constant_name=normalize_const_name(full_constant_name)@mutated_value=mutated_value@transfer_nested_constants=transfer_nested_constants@context_parts=@full_constant_name.split('::')@const_name=@context_parts.pop@reset_performed=falseenddefto_constantconst=Constant.new(full_constant_name)const.original_value=original_valueconstenddefidempotently_resetresetunless@reset_performed@reset_performed=trueendend# Hides a defined constant for the duration of an example.## @privateclassConstantHider<BaseMutatordefmutatereturnunless(@defined=recursive_const_defined?(full_constant_name))@context=recursive_const_get(@context_parts.join('::'))@original_value=get_const_defined_on(@context,@const_name)@context.__send__(:remove_const,@const_name)enddefto_constantreturnConstant.unmutated(full_constant_name)unless@definedconst=superconst.hidden=trueconst.previously_defined=trueconstenddefresetreturnunless@defined@context.const_set(@const_name,@original_value)endend# Replaces a defined constant for the duration of an example.## @privateclassDefinedConstantReplacer<BaseMutatordefinitialize(*args)super@constants_to_transfer=[]enddefmutate@context=recursive_const_get(@context_parts.join('::'))@original_value=get_const_defined_on(@context,@const_name)@constants_to_transfer=verify_constants_to_transfer!@context.__send__(:remove_const,@const_name)@context.const_set(@const_name,@mutated_value)transfer_nested_constantsenddefto_constantconst=superconst.stubbed=trueconst.previously_defined=trueconstenddefreset@constants_to_transfer.eachdo|const|@mutated_value.__send__(:remove_const,const)end@context.__send__(:remove_const,@const_name)@context.const_set(@const_name,@original_value)enddeftransfer_nested_constants@constants_to_transfer.eachdo|const|@mutated_value.const_set(const,get_const_defined_on(original_value,const))endenddefverify_constants_to_transfer!return[]unlessshould_transfer_nested_constants?{@original_value=>"the original value",@mutated_value=>"the stubbed value"}.eachdo|value,description|nextifvalue.respond_to?(:constants)raiseArgumentError,"Cannot transfer nested constants for #{@full_constant_name} "\"since #{description} is not a class or module and only classes "\"and modules support nested constants."endifArray===@transfer_nested_constants@transfer_nested_constants=@transfer_nested_constants.map(&:to_s)ifRUBY_VERSION=='1.8.7'undefined_constants=@transfer_nested_constants-constants_defined_on(@original_value)ifundefined_constants.any?available_constants=constants_defined_on(@original_value)-@transfer_nested_constantsraiseArgumentError,"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_constantselseconstants_defined_on(@original_value)endenddefshould_transfer_nested_constants?returntrueif@transfer_nested_constantsreturnfalseunlessRSpec::Mocks.configuration.transfer_nested_constants?@original_value.respond_to?(:constants)&&@mutated_value.respond_to?(:constants)endend# Sets an undefined constant for the duration of an example.## @privateclassUndefinedConstantSetter<BaseMutatordefmutate@parent=@context_parts.inject(Object)do|klass,name|ifconst_defined_on?(klass,name)get_const_defined_on(klass,name)elseConstantMutator.stub(name_for(klass,name),Module.new)endend@parent.const_set(@const_name,@mutated_value)enddefto_constantconst=superconst.stubbed=trueconst.previously_defined=falseconstenddefreset@parent.__send__(:remove_const,@const_name)endprivatedefname_for(parent,name)root=ifparent==Object''elseparent.nameendroot+'::'+nameendend# Uses the mutator to mutate (stub or hide) a constant. Ensures that# the mutator is correctly registered so it can be backed out at the end# of the test.## @privatedefself.mutate(mutator)::RSpec::Mocks.space.register_constant_mutator(mutator)mutator.mutateend# Used internally by the constant stubbing to raise a helpful# error when a constant like "A::B::C" is stubbed and A::B is# not a module (and thus, it's impossible to define "A::B::C"# since only modules can have nested constants).## @api privatedefself.raise_on_invalid_constlambdado|const_name,failed_name|raise"Cannot stub constant #{failed_name} on #{const_name} "\"since #{const_name} is not a module."endendendendend