rspec-mocks 2.14.0.rc1 → 2.14.0

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