rspec-mocks 2.11.3 → 2.12.0

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 (51) hide show
  1. data/Changelog.md +16 -0
  2. data/README.md +26 -1
  3. data/features/argument_matchers/explicit.feature +2 -2
  4. data/features/argument_matchers/general_matchers.feature +4 -4
  5. data/features/argument_matchers/type_matchers.feature +1 -1
  6. data/features/message_expectations/README.md +4 -0
  7. data/features/message_expectations/any_instance.feature +1 -1
  8. data/features/message_expectations/call_original.feature +24 -0
  9. data/features/message_expectations/expect_message.feature +5 -5
  10. data/features/message_expectations/receive_counts.feature +7 -7
  11. data/features/message_expectations/warn_when_expectation_is_set_on_nil.feature +3 -3
  12. data/features/method_stubs/any_instance.feature +6 -7
  13. data/features/method_stubs/as_null_object.feature +1 -1
  14. data/features/method_stubs/simple_return_value.feature +2 -2
  15. data/features/method_stubs/stub_chain.feature +1 -1
  16. data/features/method_stubs/stub_implementation.feature +1 -1
  17. data/features/method_stubs/to_ary.feature +1 -1
  18. data/features/{stubbing_constants → mutating_constants}/README.md +21 -1
  19. data/features/mutating_constants/hiding_defined_constant.feature +64 -0
  20. data/features/{stubbing_constants → mutating_constants}/stub_defined_constant.feature +0 -0
  21. data/features/{stubbing_constants → mutating_constants}/stub_undefined_constant.feature +0 -0
  22. data/features/outside_rspec/configuration.feature +3 -3
  23. data/features/outside_rspec/standalone.feature +6 -5
  24. data/lib/rspec/mocks.rb +17 -1
  25. data/lib/rspec/mocks/any_instance.rb +12 -12
  26. data/lib/rspec/mocks/configuration.rb +28 -0
  27. data/lib/rspec/mocks/error_generator.rb +6 -0
  28. data/lib/rspec/mocks/example_methods.rb +34 -9
  29. data/lib/rspec/mocks/framework.rb +3 -2
  30. data/lib/rspec/mocks/instance_method_stasher.rb +70 -0
  31. data/lib/rspec/mocks/message_expectation.rb +49 -29
  32. data/lib/rspec/mocks/method_double.rb +84 -7
  33. data/lib/rspec/mocks/{stub_const.rb → mutate_const.rb} +117 -40
  34. data/lib/rspec/mocks/proxy.rb +16 -5
  35. data/lib/rspec/mocks/version.rb +1 -1
  36. data/spec/rspec/mocks/and_call_original_spec.rb +162 -0
  37. data/spec/rspec/mocks/any_instance_spec.rb +18 -7
  38. data/spec/rspec/mocks/configuration_spec.rb +26 -0
  39. data/spec/rspec/mocks/failing_argument_matchers_spec.rb +9 -10
  40. data/spec/rspec/mocks/instance_method_stasher_spec.rb +58 -0
  41. data/spec/rspec/mocks/mock_spec.rb +35 -35
  42. data/spec/rspec/mocks/{stub_const_spec.rb → mutate_const_spec.rb} +142 -13
  43. data/spec/rspec/mocks/null_object_mock_spec.rb +3 -2
  44. data/spec/rspec/mocks/partial_mock_spec.rb +102 -77
  45. data/spec/rspec/mocks/serialization_spec.rb +1 -2
  46. data/spec/rspec/mocks/stub_implementation_spec.rb +6 -6
  47. data/spec/rspec/mocks_spec.rb +7 -0
  48. data/spec/spec_helper.rb +11 -0
  49. metadata +79 -80
  50. data/lib/rspec/mocks/stashed_instance_method.rb +0 -60
  51. data/spec/rspec/mocks/stashed_instance_method_spec.rb +0 -53
@@ -3,7 +3,7 @@ module RSpec
3
3
  # @private
4
4
  class MethodDouble < Hash
5
5
  # @private
6
- attr_reader :method_name
6
+ attr_reader :method_name, :object
7
7
 
8
8
  # @private
9
9
  def initialize(object, method_name, proxy)
@@ -11,7 +11,7 @@ module RSpec
11
11
  @object = object
12
12
  @proxy = proxy
13
13
 
14
- @stashed_method = StashedInstanceMethod.new(object_singleton_class, @method_name)
14
+ @method_stasher = InstanceMethodStasher.new(object_singleton_class, @method_name)
15
15
  @method_is_proxied = false
16
16
  store(:expectations, [])
17
17
  store(:stubs, [])
@@ -40,6 +40,80 @@ module RSpec
40
40
  end
41
41
  end
42
42
 
43
+ # @private
44
+ def original_method
45
+ if @method_stasher.method_is_stashed?
46
+ # Example: a singleton method defined on @object
47
+ method_handle_for(@object, @method_stasher.stashed_method_name)
48
+ else
49
+ begin
50
+ # Example: an instance method defined on @object's class.
51
+ @object.class.instance_method(@method_name).bind(@object)
52
+ rescue NameError
53
+ raise unless @object.respond_to?(:superclass)
54
+
55
+ # Example: a singleton method defined on @object's superclass.
56
+ #
57
+ # Note: we have to give precedence to instance methods
58
+ # defined on @object's class, because in a case like:
59
+ #
60
+ # `klass.should_receive(:new).and_call_original`
61
+ #
62
+ # ...we want `Class#new` bound to `klass` (which will return
63
+ # an instance of `klass`), not `klass.superclass.new` (which
64
+ # would return an instance of `klass.superclass`).
65
+ original_method_from_superclass
66
+ end
67
+ end
68
+ rescue NameError
69
+ # We have no way of knowing if the object's method_missing
70
+ # will handle this message or not...but we can at least try.
71
+ # If it's not handled, a `NoMethodError` will be raised, just
72
+ # like normally.
73
+ Proc.new do |*args, &block|
74
+ @object.__send__(:method_missing, @method_name, *args, &block)
75
+ end
76
+ end
77
+
78
+ if RUBY_VERSION.to_f > 1.8
79
+ # @private
80
+ def original_method_from_superclass
81
+ @object.superclass.
82
+ singleton_class.
83
+ instance_method(@method_name).
84
+ bind(@object)
85
+ end
86
+ else
87
+ # Our implementation for 1.9 (above) causes an error on 1.8:
88
+ # TypeError: singleton method bound for a different object
89
+ #
90
+ # This doesn't work quite right in all circumstances but it's the
91
+ # best we can do.
92
+ # @private
93
+ def original_method_from_superclass
94
+ ::Kernel.warn <<-WARNING.gsub(/^ +\|/, '')
95
+ |
96
+ |WARNING: On ruby 1.8, rspec-mocks is unable to bind the original
97
+ |`#{@method_name}` method to your partial mock object (#{@object})
98
+ |for `and_call_original`. The superclass's `#{@method_name}` is being
99
+ |used instead; however, it may not work correctly when executed due
100
+ |to the fact that `self` will be #{@object.superclass}, not #{@object}.
101
+ |
102
+ |Called from: #{caller[2]}
103
+ WARNING
104
+
105
+ @object.superclass.method(@method_name)
106
+ end
107
+ end
108
+
109
+ # @private
110
+ OBJECT_METHOD_METHOD = ::Object.instance_method(:method)
111
+
112
+ # @private
113
+ def method_handle_for(object, method_name)
114
+ OBJECT_METHOD_METHOD.bind(object).call(method_name)
115
+ end
116
+
43
117
  # @private
44
118
  def object_singleton_class
45
119
  class << @object; self; end
@@ -49,7 +123,7 @@ module RSpec
49
123
  def configure_method
50
124
  RSpec::Mocks::space.add(@object) if RSpec::Mocks::space
51
125
  warn_if_nil_class
52
- @stashed_method.stash unless @method_is_proxied
126
+ @method_stasher.stash unless @method_is_proxied
53
127
  define_proxy_method
54
128
  end
55
129
 
@@ -76,7 +150,7 @@ module RSpec
76
150
  return unless @method_is_proxied
77
151
 
78
152
  object_singleton_class.__send__(:remove_method, @method_name)
79
- @stashed_method.restore
153
+ @method_stasher.restore
80
154
  @method_is_proxied = false
81
155
  end
82
156
 
@@ -104,7 +178,8 @@ module RSpec
104
178
  expectation = if existing_stub = stubs.first
105
179
  existing_stub.build_child(expected_from, 1, opts, &implementation)
106
180
  else
107
- MessageExpectation.new(error_generator, expectation_ordering, expected_from, @method_name, 1, opts, &implementation)
181
+ MessageExpectation.new(error_generator, expectation_ordering,
182
+ expected_from, self, 1, opts, &implementation)
108
183
  end
109
184
  expectations << expectation
110
185
  expectation
@@ -113,7 +188,8 @@ module RSpec
113
188
  # @private
114
189
  def add_negative_expectation(error_generator, expectation_ordering, expected_from, &implementation)
115
190
  configure_method
116
- expectation = NegativeMessageExpectation.new(error_generator, expectation_ordering, expected_from, @method_name, &implementation)
191
+ expectation = NegativeMessageExpectation.new(error_generator, expectation_ordering,
192
+ expected_from, self, &implementation)
117
193
  expectations.unshift expectation
118
194
  expectation
119
195
  end
@@ -121,7 +197,8 @@ module RSpec
121
197
  # @private
122
198
  def add_stub(error_generator, expectation_ordering, expected_from, opts={}, &implementation)
123
199
  configure_method
124
- stub = MessageExpectation.new(error_generator, expectation_ordering, expected_from, @method_name, :any, opts, &implementation)
200
+ stub = MessageExpectation.new(error_generator, expectation_ordering, expected_from,
201
+ self, :any, opts, &implementation)
125
202
  stubs.unshift stub
126
203
  stub
127
204
  end
@@ -17,14 +17,14 @@ module RSpec
17
17
  # On 1.8:
18
18
  # - C.const_get("Hash") # => ::Hash
19
19
  # - C.const_defined?("Hash") # => false
20
- # - C.constants # => ["A"]
20
+ # - C.constants # => ["B"]
21
21
  # - None of these methods accept the extra `inherit` argument
22
22
  # On 1.9:
23
23
  # - C.const_get("Hash") # => ::Hash
24
24
  # - C.const_defined?("Hash") # => true
25
25
  # - C.const_get("Hash", false) # => raises NameError
26
26
  # - C.const_defined?("Hash", false) # => false
27
- # - C.constants # => [:A]
27
+ # - C.constants # => [:B]
28
28
  # - C.constants(false) #=> []
29
29
  if Module.method(:const_defined?).arity == 1
30
30
  def const_defined_on?(mod, const_name)
@@ -70,7 +70,7 @@ module RSpec
70
70
  end
71
71
 
72
72
  # Provides information about constants that may (or may not)
73
- # have been stubbed by rspec-mocks.
73
+ # have been mutated by rspec-mocks.
74
74
  class Constant
75
75
  extend RecursiveConstMethods
76
76
 
@@ -83,12 +83,12 @@ module RSpec
83
83
  attr_reader :name
84
84
 
85
85
  # @return [Object, nil] The original value (e.g. before it
86
- # was stubbed by rspec-mocks) of the constant, or
86
+ # was mutated by rspec-mocks) of the constant, or
87
87
  # nil if the constant was not previously defined.
88
88
  attr_accessor :original_value
89
89
 
90
90
  # @api private
91
- attr_writer :previously_defined, :stubbed
91
+ attr_writer :previously_defined, :stubbed, :hidden
92
92
 
93
93
  # @return [Boolean] Whether or not the constant was defined
94
94
  # before the current example.
@@ -96,27 +96,40 @@ module RSpec
96
96
  @previously_defined
97
97
  end
98
98
 
99
+ # @return [Boolean] Whether or not rspec-mocks has mutated
100
+ # (stubbed or hidden) this constant.
101
+ def mutated?
102
+ @stubbed || @hidden
103
+ end
104
+
99
105
  # @return [Boolean] Whether or not rspec-mocks has stubbed
100
106
  # this constant.
101
107
  def stubbed?
102
108
  @stubbed
103
109
  end
104
110
 
111
+ # @return [Boolean] Whether or not rspec-mocks has hidden
112
+ # this constant.
113
+ def hidden?
114
+ @hidden
115
+ end
116
+
105
117
  def to_s
106
118
  "#<#{self.class.name} #{name}>"
107
119
  end
108
120
  alias inspect to_s
109
121
 
110
122
  # @api private
111
- def self.unstubbed(name)
123
+ def self.unmutated(name)
112
124
  const = new(name)
113
125
  const.previously_defined = recursive_const_defined?(name)
114
126
  const.stubbed = false
127
+ const.hidden = false
115
128
  const.original_value = recursive_const_get(name) if const.previously_defined?
116
129
 
117
130
  const
118
131
  end
119
- private_class_method :unstubbed
132
+ private_class_method :unmutated
120
133
 
121
134
  # Queries rspec-mocks to find out information about the named constant.
122
135
  #
@@ -124,13 +137,13 @@ module RSpec
124
137
  # @return [Constant] an object contaning information about the named
125
138
  # constant.
126
139
  def self.original(name)
127
- stubber = ConstantStubber.find(name)
128
- stubber ? stubber.to_constant : unstubbed(name)
140
+ mutator = ConstantMutator.find(name)
141
+ mutator ? mutator.to_constant : unmutated(name)
129
142
  end
130
143
  end
131
144
 
132
145
  # Provides a means to stub constants.
133
- class ConstantStubber
146
+ class ConstantMutator
134
147
  extend RecursiveConstMethods
135
148
 
136
149
  # Stubs a constant.
@@ -145,31 +158,43 @@ module RSpec
145
158
  # so you can stub constants in other contexts (e.g. helper
146
159
  # classes).
147
160
  def self.stub(constant_name, value, options = {})
148
- stubber = if recursive_const_defined?(constant_name, &raise_on_invalid_const)
161
+ mutator = if recursive_const_defined?(constant_name, &raise_on_invalid_const)
149
162
  DefinedConstantReplacer
150
163
  else
151
164
  UndefinedConstantSetter
152
165
  end
153
166
 
154
- stubber = stubber.new(constant_name, value, options[:transfer_nested_constants])
155
- stubbers << stubber
156
-
157
- stubber.stub
158
- ensure_registered_with_mocks_space
167
+ mutate(mutator.new(constant_name, value, options[:transfer_nested_constants]))
159
168
  value
160
169
  end
161
170
 
162
- # Contains common functionality used by both of the constant stubbers.
171
+ # Hides a constant.
172
+ #
173
+ # @param (see ExampleMethods#hide_const)
174
+ #
175
+ # @see ExampleMethods#hide_const
176
+ # @note It's recommended that you use `hide_const` in your
177
+ # examples. This is an alternate public API that is provided
178
+ # so you can hide constants in other contexts (e.g. helper
179
+ # classes).
180
+ def self.hide(constant_name)
181
+ return unless recursive_const_defined?(constant_name)
182
+
183
+ mutate(ConstantHider.new(constant_name, nil, { }))
184
+ nil
185
+ end
186
+
187
+ # Contains common functionality used by all of the constant mutators.
163
188
  #
164
189
  # @api private
165
- class BaseStubber
190
+ class BaseMutator
166
191
  include RecursiveConstMethods
167
192
 
168
193
  attr_reader :original_value, :full_constant_name
169
194
 
170
- def initialize(full_constant_name, stubbed_value, transfer_nested_constants)
195
+ def initialize(full_constant_name, mutated_value, transfer_nested_constants)
171
196
  @full_constant_name = full_constant_name
172
- @stubbed_value = stubbed_value
197
+ @mutated_value = mutated_value
173
198
  @transfer_nested_constants = transfer_nested_constants
174
199
  @context_parts = @full_constant_name.split('::')
175
200
  @const_name = @context_parts.pop
@@ -177,32 +202,58 @@ module RSpec
177
202
 
178
203
  def to_constant
179
204
  const = Constant.new(full_constant_name)
180
- const.stubbed = true
181
- const.previously_defined = previously_defined?
182
205
  const.original_value = original_value
183
206
 
184
207
  const
185
208
  end
186
209
  end
187
210
 
211
+ # Hides a defined constant for the duration of an example.
212
+ #
213
+ # @api private
214
+ class ConstantHider < BaseMutator
215
+ def mutate
216
+ @context = recursive_const_get(@context_parts.join('::'))
217
+ @original_value = get_const_defined_on(@context, @const_name)
218
+
219
+ @context.send(:remove_const, @const_name)
220
+ end
221
+
222
+ def to_constant
223
+ const = super
224
+ const.hidden = true
225
+ const.previously_defined = true
226
+
227
+ const
228
+ end
229
+
230
+ def rspec_reset
231
+ @context.const_set(@const_name, @original_value)
232
+ end
233
+ end
234
+
188
235
  # Replaces a defined constant for the duration of an example.
189
236
  #
190
237
  # @api private
191
- class DefinedConstantReplacer < BaseStubber
192
- def stub
238
+ class DefinedConstantReplacer < BaseMutator
239
+ def mutate
193
240
  @context = recursive_const_get(@context_parts.join('::'))
194
241
  @original_value = get_const_defined_on(@context, @const_name)
195
242
 
196
243
  constants_to_transfer = verify_constants_to_transfer!
197
244
 
198
245
  @context.send(:remove_const, @const_name)
199
- @context.const_set(@const_name, @stubbed_value)
246
+ @context.const_set(@const_name, @mutated_value)
200
247
 
201
248
  transfer_nested_constants(constants_to_transfer)
202
249
  end
203
250
 
204
- def previously_defined?
205
- true
251
+ def to_constant
252
+ const = super
253
+ const.stubbed = true
254
+ const.previously_defined = true
255
+
256
+ const
206
257
  end
207
258
 
208
259
  def rspec_reset
@@ -212,14 +263,14 @@ module RSpec
212
263
 
213
264
  def transfer_nested_constants(constants)
214
265
  constants.each do |const|
215
- @stubbed_value.const_set(const, get_const_defined_on(original_value, const))
266
+ @mutated_value.const_set(const, get_const_defined_on(original_value, const))
216
267
  end
217
268
  end
218
269
 
219
270
  def verify_constants_to_transfer!
220
271
  return [] unless @transfer_nested_constants
221
272
 
222
- { @original_value => "the original value", @stubbed_value => "the stubbed value" }.each do |value, description|
273
+ { @original_value => "the original value", @mutated_value => "the stubbed value" }.each do |value, description|
223
274
  unless value.respond_to?(:constants)
224
275
  raise ArgumentError,
225
276
  "Cannot transfer nested constants for #{@full_constant_name} " +
@@ -250,8 +301,8 @@ module RSpec
250
301
  # Sets an undefined constant for the duration of an example.
251
302
  #
252
303
  # @api private
253
- class UndefinedConstantSetter < BaseStubber
254
- def stub
304
+ class UndefinedConstantSetter < BaseMutator
305
+ def mutate
255
306
  remaining_parts = @context_parts.dup
256
307
  @deepest_defined_const = @context_parts.inject(Object) do |klass, name|
257
308
  break klass unless const_defined_on?(klass, name)
@@ -264,11 +315,15 @@ module RSpec
264
315
  end
265
316
 
266
317
  @const_to_remove = remaining_parts.first || @const_name
267
- context.const_set(@const_name, @stubbed_value)
318
+ context.const_set(@const_name, @mutated_value)
268
319
  end
269
320
 
270
- def previously_defined?
271
- false
321
+ def to_constant
322
+ const = super
323
+ const.stubbed = true
324
+ const.previously_defined = false
325
+
326
+ const
272
327
  end
273
328
 
274
329
  def rspec_reset
@@ -276,6 +331,17 @@ module RSpec
276
331
  end
277
332
  end
278
333
 
334
+ # Uses the mutator to mutate (stub or hide) a constant. Ensures that
335
+ # the mutator is correctly registered so it can be backed out at the end
336
+ # of the test.
337
+ #
338
+ # @api private
339
+ def self.mutate(mutator)
340
+ register_mutator(mutator)
341
+ mutator.mutate
342
+ ensure_registered_with_mocks_space
343
+ end
344
+
279
345
  # Ensures the constant stubbing is registered with
280
346
  # rspec-mocks space so that stubbed constants can
281
347
  # be restored when examples finish.
@@ -297,21 +363,26 @@ module RSpec
297
363
  # We use reverse order so that if the same constant
298
364
  # was stubbed multiple times, the original value gets
299
365
  # properly restored.
300
- stubbers.reverse.each { |s| s.rspec_reset }
366
+ mutators.reverse.each { |s| s.rspec_reset }
301
367
 
302
- stubbers.clear
368
+ mutators.clear
303
369
  end
304
370
 
305
- # The list of constant stubbers that have been used for
371
+ # The list of constant mutators that have been used for
306
372
  # the current example.
307
373
  #
308
374
  # @api private
309
- def self.stubbers
310
- @stubbers ||= []
375
+ def self.mutators
376
+ @mutators ||= []
377
+ end
378
+
379
+ # @api private
380
+ def self.register_mutator(mutator)
381
+ mutators << mutator
311
382
  end
312
383
 
313
384
  def self.find(name)
314
- stubbers.find { |s| s.full_constant_name == name }
385
+ mutators.find { |s| s.full_constant_name == name }
315
386
  end
316
387
 
317
388
  # Used internally by the constant stubbing to raise a helpful
@@ -327,6 +398,12 @@ module RSpec
327
398
  end
328
399
  end
329
400
  end
401
+
402
+ # Keeps backwards compatibility since we had released an rspec-mocks that
403
+ # only supported stubbing. Later, we released the hide_const feature and
404
+ # decided that the term "mutator" was a better term to wrap up the concept
405
+ # of both stubbing and hiding.
406
+ ConstantStubber = ConstantMutator
330
407
  end
331
408
  end
332
409