aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport/lib/active_support/vendor/flexmock.rb
blob: ad6877715af9575590d4001309f9d4d248ba42e9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
#!/usr/bin/env ruby

#---
# Copyright 2003, 2004, 2005, 2006 by Jim Weirich (jim@weirichhouse.org).
# All rights reserved.

# Permission is granted for use, copying, modification, distribution,
# and distribution of modified versions of this work as long as the
# above copyright notice is included.
#+++

require 'test/unit'

######################################################################
# FlexMock is a flexible mock object suitable for using with Ruby's
# Test::Unit unit test framework.  FlexMock has a simple interface
# that's easy to remember, and leaves the hard stuff to all those
# other mock object implementations.
#
# Basic Usage:
#
#   m = FlexMock.new("name")
#   m.mock_handle(:meth) { |args| assert_stuff }
#
# Simplified Usage:
#
#   m = FlexMock.new("name")
#   m.should_receive(:upcase).with("stuff").
#     returns("STUFF")
#   m.should_receive(:downcase).with(String).
#     returns { |s| s.downcase }.once
#
# With Test::Unit Integration:
#
#   class TestSomething < Test::Unit::TestCase
#     include FlexMock::TestCase
#
#     def test_something
#       m = flexmock("name")
#       m.should_receive(:hi).and_return("Hello")
#       m.hi
#     end
#   end
#
# Note: When using Test::Unit integeration, don't forget to include
# FlexMock::TestCase.  Also, if you override +teardown+, make sure you
# call +super+.
#
class FlexMock
  include Test::Unit::Assertions

  class BadInterceptionError < RuntimeError; end

  attr_reader :mock_name, :mock_groups
  attr_accessor :mock_current_order

  # Create a FlexMock object with the given name.  The name is used in
  # error messages.
  def initialize(name="unknown")
    @mock_name = name
    @expectations = Hash.new
    @allocated_order = 0
    @mock_current_order = 0
    @mock_groups = {}
    @ignore_missing = false
    @verified = false
  end
  
  # Handle all messages denoted by +sym+ by calling the given block
  # and passing any parameters to the block.  If we know exactly how
  # many calls are to be made to a particular method, we may check
  # that by passing in the number of expected calls as a second
  # paramter.
  def mock_handle(sym, expected_count=nil, &block)
    self.should_receive(sym).times(expected_count).returns(&block)
  end

  # Verify that each method that had an explicit expected count was
  # actually called that many times.
  def mock_verify
    return if @verified
    @verified = true
    mock_wrap do
      @expectations.each do |sym, handler|
        handler.mock_verify
      end
    end
  end

  # Teardown and infrastructure setup for this mock.
  def mock_teardown
  end

  # Allocation a new order number from the mock.
  def mock_allocate_order
    @auto_allocate = true
    @allocated_order += 1
  end

  # Ignore all undefined (missing) method calls.
  def should_ignore_missing
    @ignore_missing = true
  end
  alias mock_ignore_missing should_ignore_missing

  # Handle missing methods by attempting to look up a handler.
  def method_missing(sym, *args, &block)
    mock_wrap do
      if handler = @expectations[sym]
        args << block  if block_given?
        handler.call(*args)
      else
        super(sym, *args, &block)  unless @ignore_missing
      end
    end
  end

  # Save the original definition of respond_to? for use a bit later.
  alias mock_respond_to? respond_to?

  # Override the built-in respond_to? to include the mocked methods.
  def respond_to?(sym)
    super || (@expectations[sym] ? true : @ignore_missing)
  end

  # Override the built-in +method+ to include the mocked methods.
  def method(sym)
    @expectations[sym] || super
  rescue NameError => ex
    if @ignore_missing
      proc { }
    else
      raise ex
    end
  end

  # Declare that the mock object should receive a message with the
  # given name.  An expectation object for the method name is returned
  # as the result of this method.  Further expectation constraints can
  # be added by chaining to the result.
  #
  # See Expectation for a list of declarators that can be used.
  def should_receive(sym)
    @expectations[sym] ||= ExpectationDirector.new(sym)
    result = Expectation.new(self, sym)
    @expectations[sym] << result
    override_existing_method(sym) if mock_respond_to?(sym)
    result
  end

  # Override the existing definition of method +sym+ in the mock.
  # Most methods depend on the method_missing trick to be invoked.
  # However, if the method already exists, it will not call
  # method_missing.  This method defines a singleton method on the
  # mock to explicitly invoke the method_missing logic.
  def override_existing_method(sym)
    sclass.class_eval "def #{sym}(*args, &block) method_missing(:#{sym}, *args, &block) end"
  end
  private :override_existing_method
  
  # Return the singleton class of the mock object.
  def sclass
    class << self; self; end
  end
  private :sclass

  # Declare that the mock object should expect methods by providing a
  # recorder for the methods and having the user invoke the expected
  # methods in a block.  Further expectations may be applied the
  # result of the recording call.
  #
  # Example Usage:
  #
  #   mock.should_expect do |record|
  #     record.add(Integer, 4) { |a, b|
  #       a + b
  #     }.at_least.once
  #
  def should_expect
    yield Recorder.new(self)
  end

  # Return a factory object that returns this mock.  This is useful in
  # Class Interception.
  def mock_factory
    Factory.new(self)
  end

  class << self
    include Test::Unit::Assertions

    # Class method to make sure that verify is called at the end of a
    # test.  One mock object will be created for each name given to
    # the use method.  The mocks will be passed to the block as
    # arguments.  If no names are given, then a single anonymous mock
    # object will be created.
    #
    # At the end of the use block, each mock object will be verified
    # to make sure the proper number of calls have been made.
    #
    # Usage:
    #
    #   FlexMock.use("name") do |mock|    # Creates a mock named "name"
    #     mock.should_receive(:meth).
    #       returns(0).once
    #   end                               # mock is verified here
    #
    # NOTE: If you include FlexMock::TestCase into your test case
    # file, you can create mocks that will be automatically verified in
    # the test teardown by using the +flexmock+ method. 
    #
    def use(*names)
      names = ["unknown"] if names.empty?
      got_excecption = false
      mocks = names.collect { |n| new(n) }
      yield(*mocks)
    rescue Exception => ex
      got_exception = true
      raise
    ensure
      mocks.each do |mock|
        mock.mock_verify     unless got_exception
      end
    end
    
    # Class method to format a method name and argument list as a nice
    # looking string.
    def format_args(sym, args)
      if args
        "#{sym}(#{args.collect { |a| a.inspect }.join(', ')})"
      else
        "#{sym}(*args)"
      end
    end
    
    # Check will assert the block returns true.  If it doesn't, an
    # assertion failure is triggered with the given message.
    def check(msg, &block)
      assert_block(msg, &block)
    end
  end

  private

  # Wrap a block of code so the any assertion errors are wrapped so
  # that the mock name is added to the error message .
  def mock_wrap(&block)
    yield
  rescue Test::Unit::AssertionFailedError => ex
    raise Test::Unit::AssertionFailedError, 
      "in mock '#{@mock_name}': #{ex.message}",
      ex.backtrace
  end

  ####################################################################
  # A Factory object is returned from a mock_factory method call.  The
  # factory merely returns the manufactured object it is initialized
  # with.  The factory is handy to use with class interception,
  # allowing the intercepted class to return the mock object.
  #
  # If the user needs more control over the mock factory, they are
  # free to create their own.
  #
  # Typical Usage:
  #    intercept(Bar).in(Foo).with(a_mock.mack_factory)
  #
  class Factory
    def initialize(manufactured_object)
      @obj = manufactured_object
    end
    def new(*args, &block)
      @obj
    end
  end

  ####################################################################
  # The expectation director is responsible for routing calls to the
  # correct expectations for a given argument list.
  #
  class ExpectationDirector

    # Create an ExpectationDirector for a mock object.
    def initialize(sym)
      @sym = sym
      @expectations = []
      @expected_order = nil
    end

    # Invoke the expectations for a given set of arguments.
    #
    # First, look for an expectation that matches the arguements and
    # is eligible to be called.  Failing that, look for a expectation
    # that matches the arguments (at this point it will be ineligible,
    # but at least we will get a good failure message).  Finally,
    # check for expectations that don't have any argument matching
    # criteria.
    def call(*args)
      exp = @expectations.find { |e| e.match_args(args) && e.eligible? } ||
        @expectations.find { |e| e.match_args(args) } ||
        @expectations.find { |e| e.expected_args.nil? }
      FlexMock.check("no matching handler found for " +
        FlexMock.format_args(@sym, args)) { ! exp.nil? }
      exp.verify_call(*args)
    end

    # Same as call.
    def [](*args)
      call(*args)
    end

    # Append an expectation to this director.
    def <<(expectation)
      @expectations << expectation
    end

    # Do the post test verification for this directory.  Check all the
    # expectations.
    def mock_verify
      @expectations.each do |exp|
        exp.mock_verify
      end
    end
  end

  ####################################################################
  # Match any object
  class AnyMatcher
    def ===(target)
      true
    end
    def inspect
      "ANY"
    end
  end
  
  ####################################################################
  # Match only things that are equal.
  class EqualMatcher
    def initialize(obj)
      @obj = obj
    end
    def ===(target)
      @obj == target
    end
    def inspect
      "==(#{@obj.inspect})"
    end
  end
  
  ANY = AnyMatcher.new

  ####################################################################
  # Match only things where the block evaluates to true.
  class ProcMatcher
    def initialize(&block)
      @block = block
    end
    def ===(target)
      @block.call(target)
    end
    def inspect
      "on{...}"
    end
  end

  ####################################################################
  # Include this module in your test class if you wish to use the +eq+
  # and +any+ argument matching methods without a prefix.  (Otherwise
  # use <tt>FlexMock.any</tt> and <tt>FlexMock.eq(obj)</tt>.
  #
  module ArgumentTypes
    # Return an argument matcher that matches any argument.
    def any
      ANY
    end

    # Return an argument matcher that only matches things equal to
    # (==) the given object.
    def eq(obj)
      EqualMatcher.new(obj)
    end

    # Return an argument matcher that matches any object, that when
    # passed to the supplied block, will cause the block to return
    # true.
    def on(&block)
      ProcMatcher.new(&block)
    end
  end
  extend ArgumentTypes

  ####################################################################
  # Base class for all the count validators.
  #
  class CountValidator
    include Test::Unit::Assertions
    def initialize(expectation, limit)
      @exp = expectation
      @limit = limit
    end

    # If the expectation has been called +n+ times, is it still
    # eligible to be called again?  The default answer compares n to
    # the established limit.
    def eligible?(n)
      n < @limit
    end
  end

  ####################################################################
  # Validator for exact call counts.
  #
  class ExactCountValidator < CountValidator
    # Validate that the method expectation was called exactly +n+
    # times.
    def validate(n)
      assert_equal @limit, n, 
        "method '#{@exp}' called incorrect number of times"
    end
  end

  ####################################################################
  # Validator for call counts greater than or equal to a limit.
  #
  class AtLeastCountValidator < CountValidator
    # Validate the method expectation was called no more than +n+
    # times.
    def validate(n)
      assert n >= @limit,
        "Method '#{@exp}' should be called at least #{@limit} times,\n" +
        "only called #{n} times"
    end

    # If the expectation has been called +n+ times, is it still
    # eligible to be called again?  Since this validator only
    # establishes a lower limit, not an upper limit, then the answer
    # is always true.
    def eligible?(n)
      true
    end
  end

  ####################################################################
  # Validator for call counts less than or equal to a limit.
  #
  class AtMostCountValidator < CountValidator
    # Validate the method expectation was called at least +n+ times.
    def validate(n)
      assert n <= @limit,
        "Method '#{@exp}' should be called at most #{@limit} times,\n" +
        "only called #{n} times"
    end
  end

  ####################################################################
  # An Expectation is returned from each +should_receive+ message sent
  # to mock object.  Each expectation records how a message matching
  # the message name (argument to +should_receive+) and the argument
  # list (given by +with+) should behave.  Mock expectations can be
  # recorded by chaining the declaration methods defined in this
  # class.
  #
  # For example:
  #
  #   mock.should_receive(:meth).with(args).and_returns(result)
  #
  class Expectation
    include Test::Unit::Assertions

    attr_reader :expected_args, :mock, :order_number

    # Create an expectation for a method named +sym+.
    def initialize(mock, sym)
      @mock = mock
      @sym = sym
      @expected_args = nil
      @count_validators = []
      @count_validator_class = ExactCountValidator
      @actual_count = 0
      @return_value = nil
      @return_block = lambda { @return_value }
      @order_number = nil
    end

    def to_s
      FlexMock.format_args(@sym, @expected_args)
    end

    # Verify the current call with the given arguments matches the
    # expectations recorded in this object.
    def verify_call(*args)
      validate_order
      @actual_count += 1
      @return_block.call(*args)
    end

    # Is this expectation eligible to be called again?  It is eligible
    # only if all of its count validators agree that it is eligible.
    def eligible?
      @count_validators.all? { |v| v.eligible?(@actual_count) }
    end

    # Validate that the order 
    def validate_order
      return if @order_number.nil?
      FlexMock.check("method #{to_s} called out of order " +
        "(expected order #{@order_number}, was #{@mock.mock_current_order})") {
        @order_number >= @mock.mock_current_order
      }
      @mock.mock_current_order = @order_number
    end
    private :validate_order

    # Validate the correct number of calls have been made.  Called by
    # the teardown process.
    def mock_verify
      @count_validators.each do |v|
        v.validate(@actual_count)
      end
    end

    # Does the argument list match this expectation's argument
    # specification.
    def match_args(args)
      return false if @expected_args.nil?
      return false if args.size != @expected_args.size
      (0...args.size).all? { |i| match_arg(@expected_args[i], args[i]) }
    end

    # Does the expected argument match the corresponding actual value.
    def match_arg(expected, actual)
      expected === actual ||
        expected == actual ||
        ( Regexp === expected && expected === actual.to_s )
    end

    # Declare that the method should expect the given argument list.
    def with(*args)
      @expected_args = args
      self
    end

    # Declare that the method should be called with no arguments.
    def with_no_args
      with
    end

    # Declare that the method can be called with any number of
    # arguments of any type.
    def with_any_args
      @expected_args = nil
      self
    end

    # Declare that the method returns a particular value (when the
    # argument list is matched).
    #
    # * If a single value is given, it will be returned for all matching
    #   calls.
    # * If multiple values are given, each value will be returned in turn for 
    #   each successive call.  If the number of matching calls is greater
    #   than the number of values, the last value will be returned for 
    #   the extra matching calls.
    # * If a block is given, it is evaluated on each call and its 
    #   value is returned.  
    # 
    # For example:
    # 
    #  mock.should_receive(:f).returns(12)   # returns 12
    #
    #  mock.should_receive(:f).with(String). # returns an
    #    returns { |str| str.upcase }        # upcased string
    #
    # +and_return+ is an alias for +returns+.
    #
    def returns(*args, &block)
      @return_block = block_given? ? 
        block : 
        lambda { args.size == 1 ? args.first : args.shift }
      self
    end
    alias :and_return :returns  # :nodoc:

    # Declare that the method may be called any number of times.
    def zero_or_more_times
      at_least.never
    end

    # Declare that the method is called +limit+ times with the
    # declared argument list.  This may be modified by the +at_least+
    # and +at_most+ declarators.
    def times(limit)
      @count_validators << @count_validator_class.new(self, limit) unless limit.nil?
      @count_validator_class = ExactCountValidator
      self
    end

    # Declare that the method is never expected to be called with the
    # given argument list.  This may be modified by the +at_least+ and
    # +at_most+ declarators.
    def never
      times(0)
    end

    # Declare that the method is expected to be called exactly once
    # with the given argument list.  This may be modified by the
    # +at_least+ and +at_most+ declarators.
    def once
      times(1)
    end

    # Declare that the method is expected to be called exactly twice
    # with the given argument list.  This may be modified by the
    # +at_least+ and +at_most+ declarators.
    def twice
      times(2)
    end

    # Modifies the next call count declarator (+times+, +never+,
    # +once+ or +twice+) so that the declarator means the method is
    # called at least that many times.
    #
    # E.g. method f must be called at least twice:
    #
    #   mock.should_receive(:f).at_least.twice
    #
    def at_least
      @count_validator_class = AtLeastCountValidator
      self
    end

    # Modifies the next call count declarator (+times+, +never+,
    # +once+ or +twice+) so that the declarator means the method is
    # called at most that many times.
    #
    # E.g. method f must be called no more than twice
    #
    #   mock.should_receive(:f).at_most.twice
    #
    def at_most
      @count_validator_class = AtMostCountValidator
      self
    end

    # Declare that the given method must be called in order.  All
    # ordered method calls must be received in the order specified by
    # the ordering of the +should_receive+ messages.  Receiving a
    # methods out of the specified order will cause a test failure.
    #
    # If the user needs more fine control over ordering
    # (e.g. specifying that a group of messages may be received in any
    # order as long as they all come after another group of messages),
    # a _group_ _name_ may be specified in the +ordered+ calls.  All
    # messages within the same group may be received in any order.
    #
    # For example, in the following, messages +flip+ and +flop+ may be
    # received in any order (because they are in the same group), but
    # must occur strictly after +start+ but before +end+.  The message
    # +any_time+ may be received at any time because it is not
    # ordered.
    #
    #    m = FlexMock.new
    #    m.should_receive(:any_time)
    #    m.should_receive(:start).ordered
    #    m.should_receive(:flip).ordered(:flip_flop_group)
    #    m.should_receive(:flop).ordered(:flip_flop_group)
    #    m.should_receive(:end).ordered
    #
    def ordered(group_name=nil)
      if group_name.nil?
        @order_number = @mock.mock_allocate_order
      elsif (num = @mock.mock_groups[group_name])
        @order_number = num
      else
        @order_number = @mock.mock_allocate_order
        @mock.mock_groups[group_name] = @order_number
      end
      self
    end
  end

  ####################################################################
  # Translate arbitrary method calls into expectations on the given
  # mock object.
  #
  class Recorder
    include FlexMock::ArgumentTypes

    # Create a method recorder for the mock +mock+.
    def initialize(mock)
      @mock = mock
      @strict = false
    end

    # Place the record in strict mode.  While recording expectations
    # in strict mode, the following will be true.
    #
    # * All expectations will be expected in the order they were
    #   recorded.
    # * All expectations will be expected once.
    # * All arguments will be placed in exact match mode,
    #   including regular expressions and class objects.
    #
    # Strict mode is usually used when giving the recorder to a known
    # good algorithm.  Strict mode captures the exact sequence of
    # calls and validate that the code under test performs the exact
    # same sequence of calls.
    #
    # The recorder may exit strict mode via a
    # <tt>should_be_strict(false)</tt> call.  Non-strict expectations
    # may be recorded at that point, or even explicit expectations
    # (using +should_receieve+) can be specified.
    #
    def should_be_strict(is_strict=true)
      @strict = is_strict
    end

    # Is the recorder in strict mode?
    def strict?
      @strict
    end

    # Record an expectation for receiving the method +sym+ with the
    # given arguments.
    def method_missing(sym, *args, &block)
      expectation = @mock.should_receive(sym).and_return(&block)
      if @strict
        args = args.collect { |arg| eq(arg) }
        expectation.with(*args).ordered.once
      else
        expectation.with(*args)
      end
      expectation
    end
  end
  
  ####################################################################
  # Test::Unit::TestCase Integration.
  #
  # Include this module in any TestCase class in a Test::Unit test
  # suite to get integration with FlexMock.  When this module is
  # included, mocks may be created with a simple call to the
  # +flexmock+ method.  Mocks created with via the method call will
  # automatically be verified in the teardown of the test case.
  #
  # <b>Note:</b> If you define a +teardown+ method in the test case,
  # <em>dont' forget to invoke the +super+ method!</em> Failure to
  # invoke super will cause all mocks to not be verified.
  #
  module TestCase
    include ArgumentTypes

    # Teardown the test case, verifying any mocks that might have been
    # defined in this test case.
    def teardown
      super
      flexmock_teardown
    end
    
    # Do the flexmock specific teardown stuff.
    def flexmock_teardown
      @flexmock_created_mocks ||= []
      if passed?
        @flexmock_created_mocks.each do |m|
          m.mock_verify
        end
      end
    ensure
      @flexmock_created_mocks.each do |m|
        m.mock_teardown
      end
      @flexmock_interceptors ||= []
      @flexmock_interceptors.each do |i|
        i.restore
      end
    end

    # Create a FlexMock object with the given name.  Mocks created
    # with this method will be automatically verify during teardown
    # (assuming the the flexmock teardown isn't overridden).
    #
    # If a block is given, then the mock object is passed to the block and
    # may be configured in the block.
    def flexmock(name="unknown")
      mock = FlexMock.new(name)
      yield(mock) if block_given?
      flexmock_remember(mock)
      mock
    end
    
    # Stub the given object by overriding the behavior of individual methods.
    # The stub object returned will respond to the +should_receive+ 
    # method, just like normal stubs.  Singleton methods cannot be 
    # stubbed.
    #
    # Example:  Stub out DBI to return a fake db connection.
    #
    #   flexstub(DBI).should_receive(:connect).and_return {
    #     fake_db = flexmock("db connection")
    #     fake_db.should_receive(:select_all).and_return(...)
    #     fake_db
    #   }
    #
    def flexstub(obj, name=nil)
      name ||= "flexstub(#{obj.class.to_s})"
      obj.instance_eval {
        @flexmock_proxy ||= StubProxy.new(obj, FlexMock.new(name))
      }
      flexmock_remember(obj.instance_variable_get("@flexmock_proxy"))
    end
    
    # Intercept the named class in the target class for the duration
    # of the test.  Class interception is very simple-minded and has a
    # number of restrictions.  First, the intercepted class must be
    # reference in the tested class via a simple constant name
    # (e.g. no scoped names using "::") that is not directly defined
    # in the class itself.  After the test, a proxy class constant
    # will be left behind that will forward all calls to the original
    # class.
    #
    # Usage:
    #   intercept(SomeClass).in(ClassBeingTested).with(MockClass)
    #   intercept(SomeClass).with(MockClass).in(ClassBeingTested)
    #
    def intercept(intercepted_class)
      result = Interception.new(intercepted_class)
      @flexmock_interceptors ||= []
      @flexmock_interceptors << result
      result
    end
    
    private
    
    def flexmock_remember(mocking_object)
      @flexmock_created_mocks ||= []
      @flexmock_created_mocks << mocking_object
      mocking_object
    end
  end

  ####################################################################
  # A Class Interception defines a constant in the target class to be
  # a proxy that points to a replacement class for the duration of a
  # test.  When an interception is restored, the proxy will point to
  # the original intercepted class.
  #
  class Interception
    # Create an interception object with the class to intercepted.
    def initialize(intercepted_class)
      @intercepted = nil
      @target  = nil
      @replacement = nil
      @proxy = nil
      intercept(intercepted_class)
      update
    end

    # Intercept this class in the class to be tested.
    def intercept(intercepted_class)
      @intercepted = intercepted_class
      update
      self
    end
    
    # Define the class number test that will receive the
    # interceptioned definition.
    def in(target_class)
      @target = target_class
      update
      self
    end

    # Define the replacement class.  This is normally a proxy or a
    # stub.
    def with(replacement_class)
      @replacement = replacement_class
      update
      self
    end

    # Restore the original class.  The proxy remains in place however.
    def restore
      @proxy.proxied_class = @restore_class if @proxy
    end
    
    private
    
    # Update the interception if the definition is complete.
    def update
      if complete?
        do_interception
      end
    end
      
    # Is the interception definition complete.  In other words, are
    # all three actors defined?
    def complete?
      @intercepted && @target && @replacement
    end

    # Implement interception on the classes defined.
    def do_interception
      @target_class = coerce_class(@target, "target")
      @replacement_class = coerce_class(@replacement, "replacement")
      case @intercepted
      when String, Symbol
        @intercepted_name = @intercepted.to_s
      when Class
        @intercepted_name = @intercepted.name
      end
      @intercepted_class = coerce_class(@intercepted, "intercepted")
      current_class = @target_class.const_get(@intercepted_name)
      if ClassProxy === current_class
        @proxy = current_class
        @restore_class = @proxy.proxied_class
        @proxy.proxied_class = @replacement_class
      else
        @proxy = ClassProxy.new(@replacement_class)
        @restore_class = current_class
        @target_class.const_set(@intercepted_name, @proxy)
      end
    end

    # Coerce a class object, string to symbol to be the class object.
    def coerce_class(klass, where)
      case klass
      when String, Symbol
        lookup_const(klass.to_s, where)
      else
        klass
      end
    end

    def lookup_const(name, where, target=Object)
      begin
        target.const_get(name)
      rescue NameError
        raise BadInterceptionError, "in #{where} class #{name}"
      end
    end
  end

  ####################################################################
  # Class Proxy for class interception.  Forward all method calls to
  # whatever is the proxied_class.
  #
  class ClassProxy
    attr_accessor :proxied_class
    def initialize(default_class)
      @proxied_class = default_class
    end
    def method_missing(sym, *args, &block)
      @proxied_class.__send__(sym, *args, &block)
    end
  end
  
  ####################################################################
  # StubProxy is used to mate the mock framework to an existing
  # object.  The object is "enhanced" with a reference to a mock
  # object (stored in <tt>@flexmock_mock</tt>).  When the 
  # +should_receive+ method is sent to the proxy, it overrides the 
  # existing object's method by creating  singleton method that 
  # forwards to the mock.  When testing is complete, StubProxy
  # will erase the mocking infrastructure from the object being 
  # stubbed (e.g. remove instance variables and mock singleton 
  # methods).
  #
  class StubProxy
    attr_reader :mock
    
    def initialize(obj, mock)
      @obj = obj
      @mock = mock
      @method_definitions = {}
      @methods_proxied = []
    end
    
    # Stub out the given method in the existing object and then let the 
    # mock object handle should_receive.
    def should_receive(method_name)
      method_name = method_name.to_sym
      unless @methods_proxied.include?(method_name)
        hide_existing_method(method_name)
        @methods_proxied << method_name
      end
      @mock.should_receive(method_name)
    end
    
    # Verify that the mock has been properly called.  After verification, 
    # detach the mocking infrastructure from the existing object.
    def mock_verify
      @mock.mock_verify
    end

    # Remove all traces of the mocking framework from the existing object.
    def mock_teardown
      if ! detached?
        @methods_proxied.each do |method_name|
          remove_current_method(method_name)
          restore_original_definition(method_name)
        end
        @obj.instance_variable_set("@flexmock_proxy", nil)
        @obj = nil
      end
    end

    private

    # The singleton class of the object.
    def sclass
      class << @obj; self; end
    end

    # Is the current method a singleton method in the object we are
    # mocking?
    def singleton?(method_name)
      @obj.methods(false).include?(method_name.to_s)
    end

    # Hide the existing method definition with a singleton defintion
    # that proxies to our mock object.  If the current definition is a
    # singleton, we need to record the definition and remove it before
    # creating our own singleton method.  If the current definition is
    # not a singleton, all we need to do is override it with our own
    # singleton.
    def hide_existing_method(method_name)
      if singleton?(method_name)
        @method_definitions[method_name] = @obj.method(method_name)
        remove_current_method(method_name)
      end
      define_proxy_method(method_name)
    end

    # Define a proxy method that forwards to our mock object.  The
    # proxy method is defined as a singleton method on the object
    # being mocked.
    def define_proxy_method(method_name)
      sclass.class_eval %{
        def #{method_name}(*args, &block)
          @flexmock_proxy.mock.#{method_name}(*args, &block)
        end  
      }
    end

    # Restore the original singleton defintion for method_name that
    # was saved earlier.
    def restore_original_definition(method_name)
      method_def = @method_definitions[method_name]
      if method_def
        sclass.class_eval {
          define_method(method_name, &method_def)
        }
      end
    end

    # Remove the current method if it is a singleton method of the
    # object being mocked.
    def remove_current_method(method_name)
      sclass.class_eval { remove_method(method_name) }
    end

    # Have we been detached from the existing object?
    def detached?
      @obj.nil?
    end
    
  end
end