rspec-mocks 2.14.0.rc1 → 2.14.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 (35) hide show
  1. data/Changelog.md +25 -0
  2. data/features/message_expectations/allow_any_instance_of.feature +26 -0
  3. data/features/message_expectations/expect_any_instance_of.feature +27 -0
  4. data/features/message_expectations/expect_message_using_expect.feature +4 -0
  5. data/features/method_stubs/README.md +28 -2
  6. data/features/method_stubs/any_instance.feature +5 -1
  7. data/features/method_stubs/as_null_object.feature +6 -1
  8. data/features/method_stubs/simple_return_value_with_allow.feature +44 -0
  9. data/features/method_stubs/{simple_return_value.feature → simple_return_value_with_stub.feature} +0 -0
  10. data/features/method_stubs/stub_implementation.feature +23 -1
  11. data/lib/rspec/mocks.rb +35 -0
  12. data/lib/rspec/mocks/any_instance/chain.rb +27 -18
  13. data/lib/rspec/mocks/any_instance/expectation_chain.rb +1 -28
  14. data/lib/rspec/mocks/any_instance/recorder.rb +1 -2
  15. data/lib/rspec/mocks/any_instance/stub_chain.rb +2 -1
  16. data/lib/rspec/mocks/error_generator.rb +7 -0
  17. data/lib/rspec/mocks/extensions/marshal.rb +7 -7
  18. data/lib/rspec/mocks/matchers/receive.rb +5 -1
  19. data/lib/rspec/mocks/message_expectation.rb +142 -63
  20. data/lib/rspec/mocks/method_double.rb +0 -9
  21. data/lib/rspec/mocks/proxy.rb +0 -5
  22. data/lib/rspec/mocks/proxy_for_nil.rb +2 -1
  23. data/lib/rspec/mocks/syntax.rb +15 -5
  24. data/lib/rspec/mocks/test_double.rb +2 -2
  25. data/lib/rspec/mocks/version.rb +1 -1
  26. data/spec/rspec/mocks/any_instance_spec.rb +26 -0
  27. data/spec/rspec/mocks/block_return_value_spec.rb +18 -0
  28. data/spec/rspec/mocks/combining_implementation_instructions_spec.rb +205 -0
  29. data/spec/rspec/mocks/extensions/marshal_spec.rb +54 -0
  30. data/spec/rspec/mocks/matchers/receive_spec.rb +10 -0
  31. data/spec/rspec/mocks/mock_spec.rb +31 -3
  32. data/spec/rspec/mocks/partial_mock_spec.rb +3 -3
  33. data/spec/rspec/mocks/syntax_agnostic_message_matchers_spec.rb +81 -0
  34. data/spec/rspec/mocks/test_double_spec.rb +16 -6
  35. metadata +24 -9
@@ -22,7 +22,8 @@ module RSpec
22
22
  :with => [nil],
23
23
  :and_return => [:with, nil],
24
24
  :and_raise => [:with, nil],
25
- :and_yield => [:with, nil]
25
+ :and_yield => [:with, nil],
26
+ :and_call_original => [:with, nil]
26
27
  }
27
28
  end
28
29
 
@@ -116,6 +116,13 @@ module RSpec
116
116
  "method has been mocked instead of stubbed."
117
117
  end
118
118
 
119
+ def self.raise_double_negation_error(wrapped_expression)
120
+ raise "Isn't life confusing enough? You've already set a " +
121
+ "negative message expectation and now you are trying to " +
122
+ "negate it again with `never`. What does an expression like " +
123
+ "`#{wrapped_expression}.not_to receive(:msg).never` even mean?"
124
+ end
125
+
119
126
  private
120
127
 
121
128
  def intro
@@ -1,13 +1,13 @@
1
1
  module Marshal
2
2
  class << self
3
- def dump_with_mocks(*args)
4
- object = args.shift
5
-
6
- if ( ::RSpec::Mocks.space && !::RSpec::Mocks.space.registered?(object) ) || NilClass === object
7
- return dump_without_mocks(*args.unshift(object))
3
+ # Duplicates any mock objects before serialization. Otherwise,
4
+ # serialization will fail because methods exist on the singleton class.
5
+ def dump_with_mocks(object, *rest)
6
+ if ::RSpec::Mocks.space.nil? || !::RSpec::Mocks.space.registered?(object) || NilClass === object
7
+ dump_without_mocks(object, *rest)
8
+ else
9
+ dump_without_mocks(object.dup, *rest)
8
10
  end
9
-
10
- dump_without_mocks(*args.unshift(object.dup))
11
11
  end
12
12
 
13
13
  alias_method :dump_without_mocks, :dump
@@ -22,7 +22,11 @@ module RSpec
22
22
  alias matches? setup_expectation
23
23
 
24
24
  def setup_negative_expectation(subject, &block)
25
- setup_mock_proxy_method_substitute(subject, :add_negative_message_expectation, block)
25
+ # ensure `never` goes first for cases like `never.and_return(5)`,
26
+ # where `and_return` is meant to raise an error
27
+ @recorded_customizations.unshift Customization.new(:never, [], nil)
28
+
29
+ setup_expectation(subject, &block)
26
30
  end
27
31
  alias does_not_match? setup_negative_expectation
28
32
 
@@ -10,7 +10,7 @@ module RSpec
10
10
 
11
11
  # @private
12
12
  def initialize(error_generator, expectation_ordering, expected_from, method_double,
13
- expected_received_count=1, opts={}, &implementation)
13
+ expected_received_count=1, opts={}, &implementation_block)
14
14
  @error_generator = error_generator
15
15
  @error_generator.opts = opts
16
16
  @expected_from = expected_from
@@ -24,11 +24,10 @@ module RSpec
24
24
  @args_to_yield = []
25
25
  @failed_fast = nil
26
26
  @eval_context = nil
27
- @implementation = implementation
28
- @values_to_return = nil
29
- end
30
27
 
31
- # @private
28
+ @implementation = Implementation.new
29
+ self.inner_implementation_action = implementation_block
30
+ end
32
31
 
33
32
  # @private
34
33
  def expected_args
@@ -72,15 +71,20 @@ module RSpec
72
71
  # counter.stub(:count) { 1 }
73
72
  # counter.count # => 1
74
73
  def and_return(*values, &implementation)
74
+ if negative?
75
+ RSpec.deprecate "`and_return` on a negative message expectation"
76
+ end
77
+
75
78
  @expected_received_count = [@expected_received_count, values.size].max unless ignoring_args? || (@expected_received_count == 0 and @at_least)
76
79
 
77
80
  if implementation
78
81
  # TODO: deprecate `and_return { value }`
79
- @implementation = implementation
82
+ self.inner_implementation_action = implementation
80
83
  else
81
- @values_to_return = values
82
- @implementation = build_implementation
84
+ self.terminal_implementation_action = AndReturnImplementation.new(values)
83
85
  end
86
+
87
+ nil
84
88
  end
85
89
 
86
90
  # Tells the object to delegate to the original unmodified method
@@ -98,7 +102,7 @@ module RSpec
98
102
  if @method_double.object.is_a?(RSpec::Mocks::TestDouble)
99
103
  @error_generator.raise_only_valid_on_a_partial_mock(:and_call_original)
100
104
  else
101
- @implementation = @method_double.original_method
105
+ @implementation = AndCallOriginalImplementation.new(@method_double.original_method)
102
106
  end
103
107
  end
104
108
 
@@ -128,7 +132,8 @@ module RSpec
128
132
  exception = message ? exception.exception(message) : exception.exception
129
133
  end
130
134
 
131
- @implementation = Proc.new { raise exception }
135
+ self.terminal_implementation_action = Proc.new { raise exception }
136
+ nil
132
137
  end
133
138
 
134
139
  # @overload and_throw(symbol)
@@ -142,7 +147,8 @@ module RSpec
142
147
  # car.stub(:go).and_throw(:out_of_gas)
143
148
  # car.stub(:go).and_throw(:out_of_gas, :level => 0.1)
144
149
  def and_throw(*args)
145
- @implementation = Proc.new { throw(*args) }
150
+ self.terminal_implementation_action = Proc.new { throw(*args) }
151
+ nil
146
152
  end
147
153
 
148
154
  # Tells the object to yield one or more args to a block when the message
@@ -154,7 +160,7 @@ module RSpec
154
160
  def and_yield(*args, &block)
155
161
  yield @eval_context = Object.new.extend(RSpec::Mocks::InstanceExec) if block
156
162
  @args_to_yield << args
157
- @implementation = build_implementation
163
+ self.initial_implementation_action = AndYieldImplementation.new(@args_to_yield, @eval_context, @error_generator)
158
164
  self
159
165
  end
160
166
 
@@ -165,7 +171,7 @@ module RSpec
165
171
 
166
172
  # @private
167
173
  def invoke(parent_stub, *args, &block)
168
- if (@expected_received_count == 0 && !@at_least) || ((@exactly || @at_most) && (@actual_received_count == @expected_received_count))
174
+ if negative? || ((@exactly || @at_most) && (@actual_received_count == @expected_received_count))
169
175
  @actual_received_count += 1
170
176
  @failed_fast = true
171
177
  #args are the args we actually received, @argument_list_matcher is the
@@ -176,8 +182,8 @@ module RSpec
176
182
  @order_group.handle_order_constraint self
177
183
 
178
184
  begin
179
- if @implementation
180
- @implementation.call(*args, &block)
185
+ if implementation.present?
186
+ implementation.call(*args, &block)
181
187
  elsif parent_stub
182
188
  parent_stub.invoke(nil, *args, &block)
183
189
  end
@@ -186,6 +192,11 @@ module RSpec
186
192
  end
187
193
  end
188
194
 
195
+ # @private
196
+ def negative?
197
+ @expected_received_count == 0 && !@at_least
198
+ end
199
+
189
200
  # @private
190
201
  def called_max_times?
191
202
  @expected_received_count != :any &&
@@ -291,7 +302,7 @@ module RSpec
291
302
  # cart.add(Book.new(:isbn => 1934356379))
292
303
  # # => passes
293
304
  def with(*args, &block)
294
- @implementation = block if block_given? unless args.empty?
305
+ self.inner_implementation_action = block if block_given? unless args.empty?
295
306
  @argument_list_matcher = ArgumentListMatcher.new(*args, &block)
296
307
  self
297
308
  end
@@ -303,7 +314,7 @@ module RSpec
303
314
  #
304
315
  # dealer.should_receive(:deal_card).exactly(10).times
305
316
  def exactly(n, &block)
306
- @implementation = block if block
317
+ self.inner_implementation_action = block
307
318
  set_expected_received_count :exactly, n
308
319
  self
309
320
  end
@@ -319,7 +330,7 @@ module RSpec
319
330
  RSpec.deprecate "at_least(0) with should_receive", :replacement => "stub"
320
331
  end
321
332
 
322
- @implementation = block if block
333
+ self.inner_implementation_action = block
323
334
  set_expected_received_count :at_least, n
324
335
  self
325
336
  end
@@ -331,7 +342,7 @@ module RSpec
331
342
  #
332
343
  # dealer.should_receive(:deal_card).at_most(10).times
333
344
  def at_most(n, &block)
334
- @implementation = block if block
345
+ self.inner_implementation_action = block
335
346
  set_expected_received_count :at_most, n
336
347
  self
337
348
  end
@@ -344,7 +355,7 @@ module RSpec
344
355
  # dealer.should_receive(:deal_card).at_least(10).times
345
356
  # dealer.should_receive(:deal_card).at_most(10).times
346
357
  def times(&block)
347
- @implementation = block if block
358
+ self.inner_implementation_action = block
348
359
  self
349
360
  end
350
361
 
@@ -352,7 +363,7 @@ module RSpec
352
363
  # Allows an expected message to be received any number of times.
353
364
  def any_number_of_times(&block)
354
365
  RSpec.deprecate "any_number_of_times", :replacement => "stub"
355
- @implementation = block if block
366
+ self.inner_implementation_action = block
356
367
  @expected_received_count = :any
357
368
  self
358
369
  end
@@ -363,6 +374,7 @@ module RSpec
363
374
  #
364
375
  # car.should_receive(:stop).never
365
376
  def never
377
+ ErrorGenerator.raise_double_negation_error("expect(obj)") if negative?
366
378
  @expected_received_count = 0
367
379
  self
368
380
  end
@@ -373,7 +385,7 @@ module RSpec
373
385
  #
374
386
  # car.should_receive(:go).once
375
387
  def once(&block)
376
- @implementation = block if block
388
+ self.inner_implementation_action = block
377
389
  set_expected_received_count :exactly, 1
378
390
  self
379
391
  end
@@ -384,7 +396,7 @@ module RSpec
384
396
  #
385
397
  # car.should_receive(:go).twice
386
398
  def twice(&block)
387
- @implementation = block if block
399
+ self.inner_implementation_action = block
388
400
  set_expected_received_count :exactly, 2
389
401
  self
390
402
  end
@@ -397,7 +409,7 @@ module RSpec
397
409
  # api.should_receive(:run).ordered
398
410
  # api.should_receive(:finish).ordered
399
411
  def ordered(&block)
400
- @implementation = block if block
412
+ self.inner_implementation_action = block
401
413
  @order_group.register(self)
402
414
  @ordered = true
403
415
  self
@@ -405,7 +417,7 @@ module RSpec
405
417
 
406
418
  # @private
407
419
  def negative_expectation_for?(message)
408
- return false
420
+ @message == message && negative?
409
421
  end
410
422
 
411
423
  # @private
@@ -418,7 +430,7 @@ module RSpec
418
430
  @actual_received_count += 1
419
431
  end
420
432
 
421
- protected
433
+ private
422
434
 
423
435
  def failed_fast?
424
436
  @failed_fast
@@ -435,58 +447,33 @@ module RSpec
435
447
  end
436
448
  end
437
449
 
438
- private
439
-
440
- def build_implementation
441
- Implementation.new(
442
- @values_to_return, @args_to_yield,
443
- @eval_context, @error_generator
444
- ).method(:call)
445
- end
446
- end
447
-
448
- # @private
449
- class NegativeMessageExpectation < MessageExpectation
450
- # @private
451
- def initialize(error_generator, expectation_ordering, expected_from, method_double, &implementation)
452
- super(error_generator, expectation_ordering, expected_from, method_double, 0, {}, &implementation)
450
+ def initial_implementation_action=(action)
451
+ implementation.initial_action = action
453
452
  end
454
453
 
455
- # no-op
456
- # @deprecated and_return is not supported with negative message expectations.
457
- def and_return(*)
458
- RSpec.deprecate "and_return with should_not_receive"
454
+ def inner_implementation_action=(action)
455
+ implementation.inner_action = action if action
459
456
  end
460
457
 
461
- # @private
462
- def negative_expectation_for?(message)
463
- return @message == message
458
+ def terminal_implementation_action=(action)
459
+ implementation.terminal_action = action
464
460
  end
465
461
  end
466
462
 
467
- # Represents a configured implementation. Takes into account
468
- # `and_return` and `and_yield` instructions.
463
+ # Handles the implementation of an `and_yield` declaration.
469
464
  # @private
470
- class Implementation
471
- def initialize(values_to_return, args_to_yield, eval_context, error_generator)
472
- @values_to_return = values_to_return
465
+ class AndYieldImplementation
466
+ def initialize(args_to_yield, eval_context, error_generator)
473
467
  @args_to_yield = args_to_yield
474
468
  @eval_context = eval_context
475
469
  @error_generator = error_generator
476
470
  end
477
471
 
478
- def call(*args_to_ignore, &block)
479
- default_return_value = perform_yield(&block)
480
- return default_return_value unless @values_to_return
481
-
482
- if @values_to_return.size > 1
483
- @values_to_return.shift
484
- else
485
- @values_to_return.first
486
- end
472
+ def arity
473
+ -1
487
474
  end
488
475
 
489
- def perform_yield(&block)
476
+ def call(*args_to_ignore, &block)
490
477
  return if @args_to_yield.empty? && @eval_context.nil?
491
478
 
492
479
  @error_generator.raise_missing_block_error @args_to_yield unless block
@@ -500,5 +487,97 @@ module RSpec
500
487
  value
501
488
  end
502
489
  end
490
+
491
+ # Handles the implementation of an `and_return` implementation.
492
+ # @private
493
+ class AndReturnImplementation
494
+ def initialize(values_to_return)
495
+ @values_to_return = values_to_return
496
+ end
497
+
498
+ def arity
499
+ -1
500
+ end
501
+
502
+ def call(*args_to_ignore, &block)
503
+ if @values_to_return.size > 1
504
+ @values_to_return.shift
505
+ else
506
+ @values_to_return.first
507
+ end
508
+ end
509
+ end
510
+
511
+ # Represents a configured implementation. Takes into account
512
+ # any number of sub-implementations.
513
+ # @private
514
+ class Implementation
515
+ attr_accessor :initial_action, :inner_action, :terminal_action
516
+
517
+ def call(*args, &block)
518
+ actions.map do |action|
519
+ action.call(*arg_slice_for(args, action.arity), &block)
520
+ end.last
521
+ end
522
+
523
+ def arg_slice_for(args, arity)
524
+ if arity >= 0
525
+ args.slice(0, arity)
526
+ else
527
+ args
528
+ end
529
+ end
530
+
531
+ def present?
532
+ actions.any?
533
+ end
534
+
535
+ private
536
+
537
+ def actions
538
+ [initial_action, inner_action, terminal_action].compact
539
+ end
540
+ end
541
+
542
+ # Represents an `and_call_original` implementation.
543
+ # @private
544
+ class AndCallOriginalImplementation
545
+ def initialize(method)
546
+ @method = method
547
+ end
548
+
549
+ CannotModifyFurtherError = Class.new(StandardError)
550
+
551
+ def arity
552
+ @method.arity
553
+ end
554
+
555
+ def initial_action=(value)
556
+ raise cannot_modify_further_error
557
+ end
558
+
559
+ def inner_action=(value)
560
+ raise cannot_modify_further_error
561
+ end
562
+
563
+ def terminal_action=(value)
564
+ raise cannot_modify_further_error
565
+ end
566
+
567
+ def present?
568
+ true
569
+ end
570
+
571
+ def call(*args, &block)
572
+ @method.call(*args, &block)
573
+ end
574
+
575
+ private
576
+
577
+ def cannot_modify_further_error
578
+ CannotModifyFurtherError.new "This method has already been configured " +
579
+ "to call the original implementation, and cannot be modified further."
580
+ end
581
+ end
503
582
  end
504
583
  end
@@ -226,15 +226,6 @@ module RSpec
226
226
  expectation
227
227
  end
228
228
 
229
- # @private
230
- def add_negative_expectation(error_generator, expectation_ordering, expected_from, &implementation)
231
- configure_method
232
- expectation = NegativeMessageExpectation.new(error_generator, expectation_ordering,
233
- expected_from, self, &implementation)
234
- expectations.unshift expectation
235
- expectation
236
- end
237
-
238
229
  # @private
239
230
  def build_expectation(error_generator, expectation_ordering)
240
231
  expected_from = IGNORED_BACKTRACE_LINE