rspec-mocks 2.11.3 → 2.12.0

Sign up to get free protection for your applications and to get access to all the features.
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