rspec-mocks 3.0.0.beta1 → 3.0.0.beta2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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"