Updated to latest rspec
[gitorious:georgyos-clone.git] / vendor / plugins / rspec / lib / spec / mocks / proxy.rb
1 module Spec
2   module Mocks
3     class Proxy
4       DEFAULT_OPTIONS = {
5         :null_object => false,
6       }
7
8       def initialize(target, name, options={})
9         @target = target
10         @name = name
11         @error_generator = ErrorGenerator.new target, name
12         @expectation_ordering = OrderGroup.new @error_generator
13         @expectations = []
14         @messages_received = []
15         @stubs = []
16         @proxied_methods = []
17         @options = options ? DEFAULT_OPTIONS.dup.merge(options) : DEFAULT_OPTIONS
18       end
19
20       def null_object?
21         @options[:null_object]
22       end
23
24       def add_message_expectation(expected_from, sym, opts={}, &block)
25         __add sym
26         @expectations << MessageExpectation.new(@error_generator, @expectation_ordering, expected_from, sym, block_given? ? block : nil, 1, opts)
27         @expectations.last
28       end
29
30       def add_negative_message_expectation(expected_from, sym, &block)
31         __add sym
32         @expectations << NegativeMessageExpectation.new(@error_generator, @expectation_ordering, expected_from, sym, block_given? ? block : nil)
33         @expectations.last
34       end
35
36       def add_stub(expected_from, sym, opts={})
37         __add sym
38         @stubs.unshift MessageExpectation.new(@error_generator, @expectation_ordering, expected_from, sym, nil, :any, opts)
39         @stubs.first
40       end
41
42       def verify #:nodoc:
43         verify_expectations
44       ensure
45         reset
46       end
47
48       def reset
49         clear_expectations
50         clear_stubs
51         reset_proxied_methods
52         clear_proxied_methods
53       end
54
55       def received_message?(sym, *args, &block)
56         @messages_received.any? {|array| array == [sym, args, block]}
57       end
58
59       def has_negative_expectation?(sym)
60         @expectations.detect {|expectation| expectation.negative_expectation_for?(sym)}
61       end
62
63       def message_received(sym, *args, &block)
64         if expectation = find_matching_expectation(sym, *args)
65           expectation.invoke(args, block)
66         elsif (stub = find_matching_method_stub(sym, *args))
67           if expectation = find_almost_matching_expectation(sym, *args)
68             expectation.advise(args, block) unless expectation.expected_messages_received?
69           end
70           stub.invoke([], block)
71         elsif expectation = find_almost_matching_expectation(sym, *args)
72           expectation.advise(args, block) if null_object? unless expectation.expected_messages_received?
73           raise_unexpected_message_args_error(expectation, *args) unless (has_negative_expectation?(sym) or null_object?)
74         else
75           @target.send :method_missing, sym, *args, &block
76         end
77       end
78
79       def raise_unexpected_message_args_error(expectation, *args)
80         @error_generator.raise_unexpected_message_args_error expectation, *args
81       end
82
83       def raise_unexpected_message_error(sym, *args)
84         @error_generator.raise_unexpected_message_error sym, *args
85       end
86       
87     private
88
89       def __add(sym)
90         $rspec_mocks.add(@target) unless $rspec_mocks.nil?
91         define_expected_method(sym)
92       end
93       
94       def define_expected_method(sym)
95         visibility_string = "#{visibility(sym)} :#{sym}"
96         if target_responds_to?(sym) && !target_metaclass.method_defined?(munge(sym))
97           munged_sym = munge(sym)
98           target_metaclass.instance_eval do
99             alias_method munged_sym, sym if method_defined?(sym.to_s)
100           end
101           @proxied_methods << sym
102         end
103         
104         target_metaclass.class_eval(<<-EOF, __FILE__, __LINE__)
105           def #{sym}(*args, &block)
106             __mock_proxy.message_received :#{sym}, *args, &block
107           end
108           #{visibility_string}
109         EOF
110       end
111
112       def target_responds_to?(sym)
113         return @target.send(munge(:respond_to?),sym) if @already_proxied_respond_to
114         return @already_proxied_respond_to = true if sym == :respond_to?
115         return @target.respond_to?(sym)
116       end
117
118       def visibility(sym)
119         if Mock === @target
120           'public'
121         elsif target_metaclass.private_method_defined?(sym)
122           'private'
123         elsif target_metaclass.protected_method_defined?(sym)
124           'protected'
125         else
126           'public'
127         end
128       end
129
130       def munge(sym)
131         "proxied_by_rspec__#{sym.to_s}".to_sym
132       end
133
134       def clear_expectations
135         @expectations.clear
136       end
137
138       def clear_stubs
139         @stubs.clear
140       end
141
142       def clear_proxied_methods
143         @proxied_methods.clear
144       end
145
146       def target_metaclass
147         class << @target; self; end
148       end
149
150       def verify_expectations
151         @expectations.each do |expectation|
152           expectation.verify_messages_received
153         end
154       end
155
156       def reset_proxied_methods
157         @proxied_methods.each do |sym|
158           munged_sym = munge(sym)
159           target_metaclass.instance_eval do
160             if method_defined?(munged_sym.to_s)
161               alias_method sym, munged_sym
162               undef_method munged_sym
163             else
164               undef_method sym
165             end
166           end
167         end
168       end
169
170       def find_matching_expectation(sym, *args)
171         @expectations.find {|expectation| expectation.matches(sym, args)}
172       end
173
174       def find_almost_matching_expectation(sym, *args)
175         @expectations.find {|expectation| expectation.matches_name_but_not_args(sym, args)}
176       end
177
178       def find_matching_method_stub(sym, *args)
179         @stubs.find {|stub| stub.matches(sym, args)}
180       end
181
182     end
183   end
184 end