rspec-mocks 3.0.0.beta1 → 3.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. data.tar.gz.sig +1 -1
  2. data/Changelog.md +95 -3
  3. data/README.md +27 -13
  4. data/features/README.md +15 -7
  5. data/features/argument_matchers/README.md +5 -5
  6. data/features/argument_matchers/explicit.feature +6 -6
  7. data/features/argument_matchers/general_matchers.feature +4 -4
  8. data/features/argument_matchers/type_matchers.feature +2 -2
  9. data/features/message_expectations/README.md +29 -27
  10. data/features/message_expectations/call_original.feature +0 -1
  11. data/features/message_expectations/message_chains_using_expect.feature +49 -0
  12. data/features/method_stubs/README.md +2 -1
  13. data/features/method_stubs/{any_instance.feature → allow_any_instance_of.feature} +12 -12
  14. data/features/method_stubs/{stub_chain.feature → receive_message_chain.feature} +3 -3
  15. data/features/method_stubs/to_ary.feature +1 -1
  16. data/features/mutating_constants/stub_defined_constant.feature +0 -1
  17. data/features/outside_rspec/standalone.feature +1 -1
  18. data/features/spies/spy_pure_mock_method.feature +1 -1
  19. data/features/test_frameworks/test_unit.feature +21 -10
  20. data/features/verifying_doubles/README.md +17 -0
  21. data/features/verifying_doubles/class_doubles.feature +1 -16
  22. data/features/verifying_doubles/dynamic_classes.feature +0 -1
  23. data/features/verifying_doubles/{introduction.feature → instance_doubles.feature} +41 -23
  24. data/features/verifying_doubles/partial_doubles.feature +2 -2
  25. data/lib/rspec/mocks.rb +69 -82
  26. data/lib/rspec/mocks/any_instance/expect_chain_chain.rb +35 -0
  27. data/lib/rspec/mocks/any_instance/expectation_chain.rb +1 -2
  28. data/lib/rspec/mocks/any_instance/recorder.rb +52 -18
  29. data/lib/rspec/mocks/any_instance/stub_chain.rb +1 -1
  30. data/lib/rspec/mocks/any_instance/stub_chain_chain.rb +4 -0
  31. data/lib/rspec/mocks/argument_list_matcher.rb +10 -44
  32. data/lib/rspec/mocks/argument_matchers.rb +132 -163
  33. data/lib/rspec/mocks/configuration.rb +28 -4
  34. data/lib/rspec/mocks/error_generator.rb +46 -13
  35. data/lib/rspec/mocks/example_methods.rb +13 -12
  36. data/lib/rspec/mocks/extensions/marshal.rb +1 -1
  37. data/lib/rspec/mocks/framework.rb +3 -4
  38. data/lib/rspec/mocks/instance_method_stasher.rb +2 -3
  39. data/lib/rspec/mocks/matchers/have_received.rb +8 -6
  40. data/lib/rspec/mocks/matchers/receive.rb +28 -20
  41. data/lib/rspec/mocks/matchers/receive_message_chain.rb +65 -0
  42. data/lib/rspec/mocks/matchers/receive_messages.rb +3 -2
  43. data/lib/rspec/mocks/message_chain.rb +91 -0
  44. data/lib/rspec/mocks/message_expectation.rb +86 -80
  45. data/lib/rspec/mocks/method_double.rb +2 -11
  46. data/lib/rspec/mocks/method_reference.rb +82 -23
  47. data/lib/rspec/mocks/method_signature_verifier.rb +207 -0
  48. data/lib/rspec/mocks/mutate_const.rb +34 -50
  49. data/lib/rspec/mocks/object_reference.rb +0 -1
  50. data/lib/rspec/mocks/proxy.rb +70 -13
  51. data/lib/rspec/mocks/ruby_features.rb +24 -0
  52. data/lib/rspec/mocks/space.rb +105 -31
  53. data/lib/rspec/mocks/standalone.rb +2 -2
  54. data/lib/rspec/mocks/syntax.rb +43 -8
  55. data/lib/rspec/mocks/targets.rb +16 -7
  56. data/lib/rspec/mocks/test_double.rb +41 -15
  57. data/lib/rspec/mocks/verifying_double.rb +51 -4
  58. data/lib/rspec/mocks/verifying_message_expecation.rb +12 -12
  59. data/lib/rspec/mocks/verifying_proxy.rb +32 -19
  60. data/lib/rspec/mocks/version.rb +1 -1
  61. data/spec/rspec/mocks/and_call_original_spec.rb +28 -7
  62. data/spec/rspec/mocks/and_return_spec.rb +23 -0
  63. data/spec/rspec/mocks/and_yield_spec.rb +1 -2
  64. data/spec/rspec/mocks/any_instance_spec.rb +33 -17
  65. data/spec/rspec/mocks/array_including_matcher_spec.rb +6 -6
  66. data/spec/rspec/mocks/before_all_spec.rb +132 -0
  67. data/spec/rspec/mocks/block_return_value_spec.rb +12 -1
  68. data/spec/rspec/mocks/combining_implementation_instructions_spec.rb +9 -11
  69. data/spec/rspec/mocks/configuration_spec.rb +14 -1
  70. data/spec/rspec/mocks/double_spec.rb +867 -24
  71. data/spec/rspec/mocks/example_methods_spec.rb +13 -0
  72. data/spec/rspec/mocks/extensions/marshal_spec.rb +17 -17
  73. data/spec/rspec/mocks/failing_argument_matchers_spec.rb +29 -1
  74. data/spec/rspec/mocks/hash_excluding_matcher_spec.rb +12 -12
  75. data/spec/rspec/mocks/hash_including_matcher_spec.rb +21 -17
  76. data/spec/rspec/mocks/instance_method_stasher_spec.rb +2 -3
  77. data/spec/rspec/mocks/matchers/have_received_spec.rb +7 -0
  78. data/spec/rspec/mocks/matchers/receive_message_chain_spec.rb +198 -0
  79. data/spec/rspec/mocks/matchers/receive_messages_spec.rb +2 -2
  80. data/spec/rspec/mocks/matchers/receive_spec.rb +19 -6
  81. data/spec/rspec/mocks/method_signature_verifier_spec.rb +272 -0
  82. data/spec/rspec/mocks/methods_spec.rb +0 -1
  83. data/spec/rspec/mocks/multiple_return_value_spec.rb +2 -2
  84. data/spec/rspec/mocks/mutate_const_spec.rb +24 -1
  85. data/spec/rspec/mocks/nil_expectation_warning_spec.rb +6 -22
  86. data/spec/rspec/mocks/null_object_mock_spec.rb +13 -7
  87. data/spec/rspec/mocks/options_hash_spec.rb +3 -3
  88. data/spec/rspec/mocks/{partial_mock_spec.rb → partial_double_spec.rb} +5 -2
  89. data/spec/rspec/mocks/{partial_mock_using_mocks_directly_spec.rb → partial_double_using_mocks_directly_spec.rb} +1 -1
  90. data/spec/rspec/mocks/passing_argument_matchers_spec.rb +18 -0
  91. data/spec/rspec/mocks/serialization_spec.rb +1 -0
  92. data/spec/rspec/mocks/space_spec.rb +218 -7
  93. data/spec/rspec/mocks/stub_chain_spec.rb +6 -0
  94. data/spec/rspec/mocks/stub_spec.rb +0 -6
  95. data/spec/rspec/mocks/syntax_spec.rb +19 -0
  96. data/spec/rspec/mocks/test_double_spec.rb +0 -1
  97. data/spec/rspec/mocks/verifying_double_spec.rb +281 -18
  98. data/spec/rspec/mocks/verifying_message_expecation_spec.rb +7 -6
  99. data/spec/rspec/mocks_spec.rb +168 -42
  100. data/spec/spec_helper.rb +34 -22
  101. metadata +94 -63
  102. metadata.gz.sig +0 -0
  103. checksums.yaml +0 -15
  104. checksums.yaml.gz.sig +0 -2
  105. data/features/outside_rspec/configuration.feature +0 -60
  106. data/lib/rspec/mocks/arity_calculator.rb +0 -66
  107. data/lib/rspec/mocks/errors.rb +0 -12
  108. data/lib/rspec/mocks/mock.rb +0 -7
  109. data/lib/rspec/mocks/proxy_for_nil.rb +0 -37
  110. data/lib/rspec/mocks/stub_chain.rb +0 -51
  111. data/spec/rspec/mocks/argument_expectation_spec.rb +0 -32
  112. data/spec/rspec/mocks/arity_calculator_spec.rb +0 -95
  113. data/spec/rspec/mocks/mock_space_spec.rb +0 -113
  114. data/spec/rspec/mocks/mock_spec.rb +0 -788
@@ -35,15 +35,7 @@ module RSpec
35
35
 
36
36
  # @private
37
37
  def visibility
38
- if TestDouble === @object
39
- 'public'
40
- elsif object_singleton_class.private_method_defined?(@method_name)
41
- 'private'
42
- elsif object_singleton_class.protected_method_defined?(@method_name)
43
- 'protected'
44
- else
45
- 'public'
46
- end
38
+ @proxy.visibility_for(@method_name)
47
39
  end
48
40
 
49
41
  # @private
@@ -98,8 +90,7 @@ module RSpec
98
90
  # @private
99
91
  def restore_original_visibility
100
92
  return unless @original_visibility &&
101
- (object_singleton_class.method_defined?(@method_name) ||
102
- object_singleton_class.private_method_defined?(@method_name))
93
+ MethodReference.method_defined_at_any_visibility?(object_singleton_class, @method_name)
103
94
 
104
95
  object_singleton_class.__send__(*@original_visibility)
105
96
  end
@@ -1,11 +1,13 @@
1
1
  module RSpec
2
2
  module Mocks
3
- # Represents a method on a module that may or may not be defined.
3
+ # Represents a method on an object that may or may not be defined.
4
+ # The method may be an instance method on a module or a method on
5
+ # any object.
4
6
  #
5
7
  # @private
6
8
  class MethodReference
7
- def initialize(module_reference, method_name)
8
- @module_reference = module_reference
9
+ def initialize(object_reference, method_name)
10
+ @object_reference = object_reference
9
11
  @method_name = method_name
10
12
  end
11
13
 
@@ -13,43 +15,92 @@ module RSpec
13
15
  # a `NoMethodError`. It might be dynamically implemented by
14
16
  # `method_missing`.
15
17
  def implemented?
16
- @module_reference.when_loaded do |m|
18
+ @object_reference.when_loaded do |m|
17
19
  method_implemented?(m)
18
20
  end
19
21
  end
20
22
 
23
+ # Returns true if we definitively know that sending the method
24
+ # will result in a `NoMethodError`.
25
+ #
26
+ # This is not simply the inverse of `implemented?`: there are
27
+ # cases when we don't know if a method is implemented and
28
+ # both `implemented?` and `unimplemented?` will return false.
29
+ def unimplemented?
30
+ @object_reference.when_loaded do |m|
31
+ return !implemented?
32
+ end
33
+
34
+ # If it's not loaded, then it may be implemented but we can't check.
35
+ false
36
+ end
37
+
21
38
  # A method is defined if we are able to get a `Method` object for it.
22
39
  # In that case, we can assert against metadata like the arity.
23
40
  def defined?
24
- @module_reference.when_loaded do |m|
41
+ @object_reference.when_loaded do |m|
25
42
  method_defined?(m)
26
43
  end
27
44
  end
28
45
 
29
- def when_defined
46
+ def with_signature
30
47
  if original = original_method
31
- yield original
48
+ yield MethodSignature.new(original)
32
49
  end
33
50
  end
34
51
 
35
- # Yields to the block if the method is not implemented.
36
- def when_unimplemented
37
- yield unless implemented?
52
+ def visibility
53
+ @object_reference.when_loaded do |m|
54
+ return visibility_from(m)
55
+ end
56
+
57
+ # When it's not loaded, assume it's public. We don't want to
58
+ # wrongly treat the method as private.
59
+ :public
38
60
  end
39
61
 
40
62
  private
63
+
41
64
  def original_method
42
- @module_reference.when_loaded do |m|
65
+ @object_reference.when_loaded do |m|
43
66
  self.defined? && find_method(m)
44
67
  end
45
68
  end
69
+
70
+ def self.instance_method_visibility_for(klass, method_name)
71
+ if klass.public_method_defined?(method_name)
72
+ :public
73
+ elsif klass.private_method_defined?(method_name)
74
+ :private
75
+ elsif klass.protected_method_defined?(method_name)
76
+ :protected
77
+ end
78
+ end
79
+
80
+ class << self
81
+ alias method_defined_at_any_visibility? instance_method_visibility_for
82
+ end
83
+
84
+ def self.method_visibility_for(object, method_name)
85
+ instance_method_visibility_for(class << object; self; end, method_name).tap do |vis|
86
+ # If the method is not defined on the class, `instance_method_visibility_for`
87
+ # returns `nil`. However, it may be handled dynamically by `method_missing`,
88
+ # so here we check `respond_to` (passing false to not check private methods).
89
+ #
90
+ # This only considers the public case, but I don't think it's possible to
91
+ # write `method_missing` in such a way that it handles a dynamic message
92
+ # with private or protected visibility. Ruby doesn't provide you with
93
+ # the caller info.
94
+ return :public if vis.nil? && object.respond_to?(method_name, false)
95
+ end
96
+ end
46
97
  end
47
98
 
48
99
  # @private
49
100
  class InstanceMethodReference < MethodReference
50
101
  private
51
- def method_implemented?(m)
52
- m.method_defined?(@method_name)
102
+ def method_implemented?(mod)
103
+ MethodReference.method_defined_at_any_visibility?(mod, @method_name)
53
104
  end
54
105
 
55
106
  # Ideally, we'd use `respond_to?` for `method_implemented?` but we need a
@@ -66,29 +117,37 @@ module RSpec
66
117
  # This is necessary due to a bug in JRuby prior to 1.7.5 fixed in:
67
118
  # https://github.com/jruby/jruby/commit/99a0613fe29935150d76a9a1ee4cf2b4f63f4a27
68
119
  if RUBY_PLATFORM == 'java' && JRUBY_VERSION.split('.')[-1].to_i < 5
69
- def find_method(m)
70
- m.dup.instance_method(@method_name)
120
+ def find_method(mod)
121
+ mod.dup.instance_method(@method_name)
71
122
  end
72
123
  else
73
- def find_method(m)
74
- m.instance_method(@method_name)
124
+ def find_method(mod)
125
+ mod.instance_method(@method_name)
75
126
  end
76
127
  end
128
+
129
+ def visibility_from(mod)
130
+ MethodReference.instance_method_visibility_for(mod, @method_name)
131
+ end
77
132
  end
78
133
 
79
134
  # @private
80
135
  class ObjectMethodReference < MethodReference
81
136
  private
82
- def method_implemented?(m)
83
- m.respond_to?(@method_name)
137
+ def method_implemented?(object)
138
+ object.respond_to?(@method_name, true)
139
+ end
140
+
141
+ def method_defined?(object)
142
+ (class << object; self; end).method_defined?(@method_name)
84
143
  end
85
144
 
86
- def method_defined?(m)
87
- (class << m; self; end).method_defined?(@method_name)
145
+ def find_method(object)
146
+ object.method(@method_name)
88
147
  end
89
148
 
90
- def find_method(m)
91
- m.method(@method_name)
149
+ def visibility_from(object)
150
+ MethodReference.method_visibility_for(object, @method_name)
92
151
  end
93
152
  end
94
153
  end
@@ -0,0 +1,207 @@
1
+ require 'rspec/mocks/ruby_features'
2
+
3
+ module RSpec
4
+ module Mocks
5
+ # Extracts info about the number of arguments and allowed/required
6
+ # keyword args of a given method.
7
+ #
8
+ # @api private
9
+ class MethodSignature
10
+ attr_reader :min_non_kw_args, :max_non_kw_args
11
+
12
+ def initialize(method)
13
+ @method = method
14
+ classify_parameters
15
+ end
16
+
17
+ def non_kw_args_arity_description
18
+ case max_non_kw_args
19
+ when min_non_kw_args then min_non_kw_args.to_s
20
+ when INFINITY then "#{min_non_kw_args} or more"
21
+ else "#{min_non_kw_args} to #{max_non_kw_args}"
22
+ end
23
+ end
24
+
25
+ if RubyFeatures.optional_and_splat_args_supported?
26
+ def description
27
+ @description ||= begin
28
+ parts = []
29
+
30
+ unless non_kw_args_arity_description == "0"
31
+ parts << "arity of #{non_kw_args_arity_description}"
32
+ end
33
+
34
+ if @optional_kw_args.any?
35
+ parts << "optional keyword args (#{@optional_kw_args.map(&:inspect).join(", ")})"
36
+ end
37
+
38
+ if @required_kw_args.any?
39
+ parts << "required keyword args (#{@required_kw_args.map(&:inspect).join(", ")})"
40
+ end
41
+
42
+ if @allows_any_kw_args
43
+ parts << "any additional keyword args"
44
+ end
45
+
46
+ parts.join(" and ")
47
+ end
48
+ end
49
+
50
+ def missing_kw_args_from(given_kw_args)
51
+ @required_kw_args - given_kw_args
52
+ end
53
+
54
+ def invalid_kw_args_from(given_kw_args)
55
+ return [] if @allows_any_kw_args
56
+ given_kw_args - @allowed_kw_args
57
+ end
58
+
59
+ def has_kw_args_in?(args)
60
+ return false unless Hash === args.last
61
+ return false if args.count <= min_non_kw_args
62
+
63
+ @allows_any_kw_args || @allowed_kw_args.any?
64
+ end
65
+
66
+ def classify_parameters
67
+ optional_non_kw_args = @min_non_kw_args = 0
68
+ @optional_kw_args, @required_kw_args = [], []
69
+ @allows_any_kw_args = false
70
+
71
+ @method.parameters.each do |(type, name)|
72
+ case type
73
+ # def foo(a:)
74
+ when :keyreq then @required_kw_args << name
75
+ # def foo(a: 1)
76
+ when :key then @optional_kw_args << name
77
+ # def foo(**kw_args)
78
+ when :keyrest then @allows_any_kw_args = true
79
+ # def foo(a)
80
+ when :req then @min_non_kw_args += 1
81
+ # def foo(a = 1)
82
+ when :opt then optional_non_kw_args += 1
83
+ # def foo(*a)
84
+ when :rest then optional_non_kw_args = INFINITY
85
+ end
86
+ end
87
+
88
+ @max_non_kw_args = @min_non_kw_args + optional_non_kw_args
89
+ @allowed_kw_args = @required_kw_args + @optional_kw_args
90
+ end
91
+ else
92
+ def description
93
+ "arity of #{non_kw_args_arity_description}"
94
+ end
95
+
96
+ def missing_kw_args_from(given_kw_args)
97
+ []
98
+ end
99
+
100
+ def invalid_kw_args_from(given_kw_args)
101
+ []
102
+ end
103
+
104
+ def has_kw_args_in?(args)
105
+ false
106
+ end
107
+
108
+ def classify_parameters
109
+ arity = @method.arity
110
+ if arity < 0
111
+ # `~` inverts the one's complement and gives us the
112
+ # number of required args
113
+ @min_non_kw_args = ~arity
114
+ @max_non_kw_args = INFINITY
115
+ else
116
+ @min_non_kw_args = arity
117
+ @max_non_kw_args = arity
118
+ end
119
+ end
120
+ end
121
+
122
+ INFINITY = 1/0.0
123
+ end
124
+
125
+ # Deals with the slightly different semantics of block arguments.
126
+ # For methods, arguments are required unless a default value is provided.
127
+ # For blocks, arguments are optional, even if no default value is provided.
128
+ #
129
+ # However, we want to treat block args as required since you virtually always
130
+ # want to pass a value for each received argument and our `and_yield` has
131
+ # treated block args as required for many years.
132
+ #
133
+ # @api private
134
+ class BlockSignature < MethodSignature
135
+ if RubyFeatures.optional_and_splat_args_supported?
136
+ def classify_parameters
137
+ super
138
+ @min_non_kw_args = @max_non_kw_args unless @max_non_kw_args == INFINITY
139
+ end
140
+ end
141
+ end
142
+
143
+ # Figures out wheter a given method can accept various arguments.
144
+ # Surprisingly non-trivial.
145
+ #
146
+ # @api private
147
+ class MethodSignatureVerifier
148
+ # @api private
149
+ attr_reader :non_kw_args, :kw_args
150
+
151
+ def initialize(signature, args)
152
+ @signature = signature
153
+ @non_kw_args, @kw_args = split_args(*args)
154
+ end
155
+
156
+ # @api private
157
+ def valid?
158
+ missing_kw_args.empty? &&
159
+ invalid_kw_args.empty? &&
160
+ valid_non_kw_args?
161
+ end
162
+
163
+ # @api private
164
+ def error_message
165
+ if missing_kw_args.any?
166
+ "Missing required keyword arguments: %s" % [
167
+ missing_kw_args.join(", ")
168
+ ]
169
+ elsif invalid_kw_args.any?
170
+ "Invalid keyword arguments provided: %s" % [
171
+ invalid_kw_args.join(", ")
172
+ ]
173
+ elsif !valid_non_kw_args?
174
+ "Wrong number of arguments. Expected %s, got %s." % [
175
+ @signature.non_kw_args_arity_description,
176
+ non_kw_args.length
177
+ ]
178
+ end
179
+ end
180
+
181
+ private
182
+
183
+ def valid_non_kw_args?
184
+ actual = non_kw_args.length
185
+ @signature.min_non_kw_args <= actual && actual <= @signature.max_non_kw_args
186
+ end
187
+
188
+ def missing_kw_args
189
+ @signature.missing_kw_args_from(kw_args)
190
+ end
191
+
192
+ def invalid_kw_args
193
+ @signature.invalid_kw_args_from(kw_args)
194
+ end
195
+
196
+ def split_args(*args)
197
+ kw_args = if @signature.has_kw_args_in?(args)
198
+ args.pop.keys
199
+ else
200
+ []
201
+ end
202
+
203
+ [args, kw_args]
204
+ end
205
+ end
206
+ end
207
+ end
@@ -138,7 +138,6 @@ module RSpec
138
138
 
139
139
  const
140
140
  end
141
- private_class_method :unmutated
142
141
 
143
142
  # Queries rspec-mocks to find out information about the named constant.
144
143
  #
@@ -146,7 +145,7 @@ module RSpec
146
145
  # @return [Constant] an object contaning information about the named
147
146
  # constant.
148
147
  def self.original(name)
149
- mutator = ConstantMutator.find(name)
148
+ mutator = ::RSpec::Mocks.space.constant_mutator_for(name)
150
149
  mutator ? mutator.to_constant : unmutated(name)
151
150
  end
152
151
  end
@@ -190,8 +189,6 @@ module RSpec
190
189
  # so you can hide constants in other contexts (e.g. helper
191
190
  # classes).
192
191
  def self.hide(constant_name)
193
- return unless recursive_const_defined?(constant_name)
194
-
195
192
  mutate(ConstantHider.new(constant_name, nil, { }))
196
193
  nil
197
194
  end
@@ -210,6 +207,7 @@ module RSpec
210
207
  @transfer_nested_constants = transfer_nested_constants
211
208
  @context_parts = @full_constant_name.split('::')
212
209
  @const_name = @context_parts.pop
210
+ @reset_performed = false
213
211
  end
214
212
 
215
213
  def to_constant
@@ -218,6 +216,11 @@ module RSpec
218
216
 
219
217
  const
220
218
  end
219
+
220
+ def idempotently_reset
221
+ reset unless @reset_performed
222
+ @reset_performed = true
223
+ end
221
224
  end
222
225
 
223
226
  # Hides a defined constant for the duration of an example.
@@ -225,6 +228,7 @@ module RSpec
225
228
  # @api private
226
229
  class ConstantHider < BaseMutator
227
230
  def mutate
231
+ return unless @defined = recursive_const_defined?(full_constant_name)
228
232
  @context = recursive_const_get(@context_parts.join('::'))
229
233
  @original_value = get_const_defined_on(@context, @const_name)
230
234
 
@@ -232,6 +236,8 @@ module RSpec
232
236
  end
233
237
 
234
238
  def to_constant
239
+ return Constant.unmutated(full_constant_name) unless @defined
240
+
235
241
  const = super
236
242
  const.hidden = true
237
243
  const.previously_defined = true
@@ -239,7 +245,8 @@ module RSpec
239
245
  const
240
246
  end
241
247
 
242
- def rspec_reset
248
+ def reset
249
+ return unless @defined
243
250
  @context.const_set(@const_name, @original_value)
244
251
  end
245
252
  end
@@ -268,7 +275,7 @@ module RSpec
268
275
  const
269
276
  end
270
277
 
271
- def rspec_reset
278
+ def reset
272
279
  @context.__send__(:remove_const, @const_name)
273
280
  @context.const_set(@const_name, @original_value)
274
281
  end
@@ -315,19 +322,15 @@ module RSpec
315
322
  # @api private
316
323
  class UndefinedConstantSetter < BaseMutator
317
324
  def mutate
318
- remaining_parts = @context_parts.dup
319
- @deepest_defined_const = @context_parts.inject(Object) do |klass, name|
320
- break klass unless const_defined_on?(klass, name)
321
- remaining_parts.shift
322
- get_const_defined_on(klass, name)
323
- end
324
-
325
- context = remaining_parts.inject(@deepest_defined_const) do |klass, name|
326
- klass.const_set(name, Module.new)
325
+ @parent = @context_parts.inject(Object) do |klass, name|
326
+ if const_defined_on?(klass, name)
327
+ get_const_defined_on(klass, name)
328
+ else
329
+ ConstantMutator.stub(name_for(klass, name), Module.new)
330
+ end
327
331
  end
328
332
 
329
- @const_to_remove = remaining_parts.first || @const_name
330
- context.const_set(@const_name, @mutated_value)
333
+ @parent.const_set(@const_name, @mutated_value)
331
334
  end
332
335
 
333
336
  def to_constant
@@ -338,8 +341,19 @@ module RSpec
338
341
  const
339
342
  end
340
343
 
341
- def rspec_reset
342
- @deepest_defined_const.__send__(:remove_const, @const_to_remove)
344
+ def reset
345
+ @parent.__send__(:remove_const, @const_name)
346
+ end
347
+
348
+ private
349
+
350
+ def name_for(parent, name)
351
+ root = if parent == Object
352
+ ''
353
+ else
354
+ parent.name
355
+ end
356
+ root + '::' + name
343
357
  end
344
358
  end
345
359
 
@@ -349,40 +363,10 @@ module RSpec
349
363
  #
350
364
  # @api private
351
365
  def self.mutate(mutator)
352
- register_mutator(mutator)
366
+ ::RSpec::Mocks.space.register_constant_mutator(mutator)
353
367
  mutator.mutate
354
368
  end
355
369
 
356
- # Resets all stubbed constants. This is called automatically
357
- # by rspec-mocks when an example finishes.
358
- #
359
- # @api private
360
- def self.reset_all
361
- # We use reverse order so that if the same constant
362
- # was stubbed multiple times, the original value gets
363
- # properly restored.
364
- mutators.reverse.each { |s| s.rspec_reset }
365
-
366
- mutators.clear
367
- end
368
-
369
- # The list of constant mutators that have been used for
370
- # the current example.
371
- #
372
- # @api private
373
- def self.mutators
374
- @mutators ||= []
375
- end
376
-
377
- # @api private
378
- def self.register_mutator(mutator)
379
- mutators << mutator
380
- end
381
-
382
- def self.find(name)
383
- mutators.find { |s| s.full_constant_name == name }
384
- end
385
-
386
370
  # Used internally by the constant stubbing to raise a helpful
387
371
  # error when a constant like "A::B::C" is stubbed and A::B is
388
372
  # not a module (and thus, it's impossible to define "A::B::C"