rr 0.10.2 → 0.10.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,84 @@
1
+ module RR
2
+ module MethodDispatches
3
+ class BaseMethodDispatch
4
+ extend Forwardable
5
+ include Space::Reader
6
+
7
+ attr_reader :args, :block, :double
8
+
9
+ def call
10
+ raise NotImplementedError
11
+ end
12
+
13
+ protected
14
+ def find_double_to_attempt
15
+ matches = DoubleMatches.new(doubles).find_all_matches(args)
16
+
17
+ unless matches.exact_terminal_doubles_to_attempt.empty?
18
+ return matches.exact_terminal_doubles_to_attempt.first
19
+ end
20
+
21
+ unless matches.exact_non_terminal_doubles_to_attempt.empty?
22
+ return matches.exact_non_terminal_doubles_to_attempt.last
23
+ end
24
+
25
+ unless matches.wildcard_terminal_doubles_to_attempt.empty?
26
+ return matches.wildcard_terminal_doubles_to_attempt.first
27
+ end
28
+
29
+ unless matches.wildcard_non_terminal_doubles_to_attempt.empty?
30
+ return matches.wildcard_non_terminal_doubles_to_attempt.last
31
+ end
32
+
33
+ unless matches.matching_doubles.empty?
34
+ return matches.matching_doubles.first # This will raise a TimesCalledError
35
+ end
36
+
37
+ return nil
38
+ end
39
+
40
+ def call_yields
41
+ if definition.yields_value
42
+ if block
43
+ block.call(*definition.yields_value)
44
+ else
45
+ raise ArgumentError, "A Block must be passed into the method call when using yields"
46
+ end
47
+ end
48
+ end
49
+
50
+ def call_original_method_missing
51
+ subject.__send__(original_method_missing_alias_name, method_name, *args, &block)
52
+ end
53
+
54
+ def implementation_is_original_method?
55
+ double.implementation_is_original_method?
56
+ end
57
+
58
+ def extract_subject_from_return_value(return_value)
59
+ case return_value
60
+ when DoubleDefinitions::DoubleDefinition
61
+ return_value.root_subject
62
+ when DoubleDefinitions::DoubleDefinitionCreatorProxy
63
+ return_value.__creator__.root_subject
64
+ else
65
+ return_value
66
+ end
67
+ end
68
+
69
+ def double_not_found_error
70
+ message =
71
+ "On subject #{subject},\n" <<
72
+ "unexpected method invocation:\n" <<
73
+ " #{Double.formatted_name(method_name, args)}\n" <<
74
+ "expected invocations:\n" <<
75
+ Double.list_message_part(doubles)
76
+ raise Errors::DoubleNotFoundError, message
77
+ end
78
+
79
+ def_delegators :definition, :after_call_proc
80
+ def_delegators :double, :definition
81
+ def_delegators :double_injection, :doubles
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,58 @@
1
+ module RR
2
+ module MethodDispatches
3
+ class MethodDispatch < BaseMethodDispatch
4
+ attr_reader :double_injection
5
+ def initialize(double_injection, args, block)
6
+ @double_injection, @args, @block = double_injection, args, block
7
+ @double = find_double_to_attempt
8
+ end
9
+
10
+ def call
11
+ space.record_call(subject, method_name, args, block)
12
+ if double
13
+ double.method_call(args)
14
+ call_yields
15
+ return_value = extract_subject_from_return_value(call_implementation)
16
+ if after_call_proc
17
+ extract_subject_from_return_value(after_call_proc.call(return_value))
18
+ else
19
+ return_value
20
+ end
21
+ else
22
+ double_not_found_error
23
+ end
24
+ end
25
+
26
+ def call_original_method
27
+ if subject_has_original_method?
28
+ subject.__send__(original_method_alias_name, *args, &block)
29
+ elsif subject_has_original_method_missing?
30
+ call_original_method_missing
31
+ else
32
+ subject.__send__(:method_missing, method_name, *args, &block)
33
+ end
34
+ end
35
+
36
+ protected
37
+ def call_implementation
38
+ if implementation_is_original_method?
39
+ call_original_method
40
+ else
41
+ if implementation
42
+ if implementation.is_a?(Method)
43
+ implementation.call(*args, &block)
44
+ else
45
+ call_args = block ? args + [ProcFromBlock.new(&block)] : args
46
+ implementation.call(*call_args)
47
+ end
48
+ else
49
+ nil
50
+ end
51
+ end
52
+ end
53
+
54
+ def_delegators :definition, :implementation
55
+ def_delegators :double_injection, :subject_has_original_method?, :subject_has_original_method_missing?, :subject, :method_name, :original_method_alias_name
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,59 @@
1
+ module RR
2
+ module MethodDispatches
3
+ class MethodMissingDispatch < BaseMethodDispatch
4
+ class << self
5
+ def original_method_missing_alias_name
6
+ "__rr__original_method_missing"
7
+ end
8
+ end
9
+
10
+ attr_reader :subject, :method_name
11
+ def initialize(subject, method_name, args, block)
12
+ @subject, @method_name, @args, @block = subject, method_name, args, block
13
+ end
14
+
15
+ def call
16
+ if space.double_injection_exists?(subject, method_name)
17
+ space.record_call(subject, method_name, args, block)
18
+ @double = find_double_to_attempt
19
+
20
+ if double
21
+ double.method_call(args)
22
+ call_yields
23
+ return_value = extract_subject_from_return_value(call_implementation)
24
+ if after_call_proc
25
+ extract_subject_from_return_value(after_call_proc.call(return_value))
26
+ else
27
+ return_value
28
+ end
29
+ else
30
+ double_not_found_error
31
+ end
32
+ else
33
+ call_original_method
34
+ end
35
+ end
36
+
37
+ def call_original_method
38
+ double_injection.bypass_bound_method do
39
+ call_original_method_missing
40
+ end
41
+ end
42
+
43
+ protected
44
+ def call_implementation
45
+ if implementation_is_original_method?
46
+ call_original_method
47
+ else
48
+ nil
49
+ end
50
+ end
51
+
52
+ def double_injection
53
+ space.double_injection(subject, method_name)
54
+ end
55
+
56
+ def_delegators 'self.class', :original_method_missing_alias_name
57
+ end
58
+ end
59
+ end
data/lib/rr/space.rb CHANGED
@@ -19,10 +19,14 @@ module RR
19
19
  end
20
20
  end
21
21
 
22
- attr_reader :double_injections, :ordered_doubles, :recorded_calls
22
+ attr_reader :double_injections, :method_missing_injections, :ordered_doubles, :recorded_calls
23
23
  attr_accessor :trim_backtrace
24
24
  def initialize
25
- @double_injections = HashWithObjectIdKey.new
25
+ @double_injections = HashWithObjectIdKey.new do |hash, subject_object|
26
+ hash.set_with_object_id(subject_object, {})
27
+ end
28
+ @method_missing_injections = HashWithObjectIdKey.new
29
+ @singleton_method_added_injections = HashWithObjectIdKey.new
26
30
  @ordered_doubles = []
27
31
  @trim_backtrace = false
28
32
  @recorded_calls = RR::RecordedCalls.new
@@ -34,7 +38,7 @@ module RR
34
38
  # subject.
35
39
  def double_injection(subject, method_name)
36
40
  @double_injections[subject][method_name.to_sym] ||= begin
37
- DoubleInjection.new(subject, method_name.to_sym, (class << subject; self; end)).bind
41
+ Injections::DoubleInjection.new(subject, method_name.to_sym, (class << subject; self; end)).bind
38
42
  end
39
43
  end
40
44
 
@@ -42,6 +46,26 @@ module RR
42
46
  @double_injections.include?(subject) && @double_injections[subject].include?(method_name.to_sym)
43
47
  end
44
48
 
49
+ def method_missing_injection(subject)
50
+ @method_missing_injections[subject] ||= begin
51
+ Injections::MethodMissingInjection.new(subject).bind
52
+ end
53
+ end
54
+
55
+ def method_missing_injection_exists?(subject)
56
+ @method_missing_injections.include?(subject)
57
+ end
58
+
59
+ def singleton_method_added_injection(subject)
60
+ @singleton_method_added_injections[subject] ||= begin
61
+ Injections::SingletonMethodAddedInjection.new(subject).bind
62
+ end
63
+ end
64
+
65
+ def singleton_method_added_injection_exists?(subject)
66
+ @singleton_method_added_injections.include?(subject)
67
+ end
68
+
45
69
  # Registers the ordered Double to be verified.
46
70
  def register_ordered_double(double)
47
71
  @ordered_doubles << double unless ordered_doubles.include?(double)
@@ -80,6 +104,8 @@ module RR
80
104
  def reset
81
105
  reset_ordered_doubles
82
106
  reset_double_injections
107
+ reset_method_missing_injections
108
+ reset_singleton_method_added_injections
83
109
  reset_recorded_calls
84
110
  end
85
111
 
@@ -114,7 +140,21 @@ module RR
114
140
  reset_double(subject, method_name)
115
141
  end
116
142
  end
117
- end
143
+ end
144
+
145
+ def reset_method_missing_injections
146
+ @method_missing_injections.each do |subject, injection|
147
+ injection.reset
148
+ end
149
+ @method_missing_injections.clear
150
+ end
151
+
152
+ def reset_singleton_method_added_injections
153
+ @singleton_method_added_injections.each do |subject, injection|
154
+ injection.reset
155
+ end
156
+ @singleton_method_added_injections.clear
157
+ end
118
158
 
119
159
  def reset_recorded_calls
120
160
  @recorded_calls.clear
@@ -1,7 +1,7 @@
1
1
  module RR
2
2
  class SpyVerificationProxy
3
3
  instance_methods.each do |m|
4
- unless m =~ /^_/ || m.to_s == 'object_id' || m.to_s == "instance_eval" || m.to_s == 'respond_to?'
4
+ unless m =~ /^_/ || m.to_s == 'object_id' || m.to_s == "instance_eval" || m.to_s == "instance_exec" || m.to_s == 'respond_to?'
5
5
  alias_method "__blank_slated_#{m}", m
6
6
  undef_method m
7
7
  end
@@ -1,95 +1,552 @@
1
1
  require File.expand_path("#{File.dirname(__FILE__)}/../../spec_helper")
2
2
 
3
3
  module RR
4
- describe DoubleInjection do
5
- attr_reader :subject, :method_name, :double_injection
6
- macro("sets up object and method_name") do
7
- it "sets up object and method_name" do
8
- double_injection.subject.should === subject
9
- double_injection.method_name.should == method_name.to_sym
4
+ module Injections
5
+ describe DoubleInjection do
6
+ attr_reader :subject, :method_name, :double_injection
7
+ macro("sets up subject and method_name") do
8
+ it "sets up subject and method_name" do
9
+ double_injection.subject.should === subject
10
+ double_injection.method_name.should == method_name.to_sym
11
+ end
10
12
  end
11
- end
12
13
 
13
- describe "#initialize" do
14
- context "when method_name is a symbol" do
15
- send("sets up object and method_name")
14
+ describe "mock/stub" do
15
+ context "when the subject responds to the injected method" do
16
+ before do
17
+ @subject = Object.new
18
+ class << subject
19
+ attr_reader :original_foobar_called
16
20
 
17
- before do
18
- @subject = Object.new
19
- @method_name = :foobar
20
- subject.methods.should_not include(method_name.to_s)
21
- @double_injection = DoubleInjection.new(subject, method_name, (class << subject; self; end))
22
- end
23
- end
21
+ def foobar
22
+ @original_foobar_called = true
23
+ :original_foobar
24
+ end
25
+ end
24
26
 
25
- context "when method_name is a string" do
26
- before do
27
- @subject = Object.new
28
- @method_name = 'foobar'
29
- subject.methods.should_not include(method_name)
30
- @double_injection = DoubleInjection.new(subject, method_name, (class << subject; self; end))
31
- end
27
+ def subject.foobar
28
+ :original_foobar
29
+ end
32
30
 
33
- send("sets up object and method_name")
34
- end
31
+ subject.should respond_to(:foobar)
32
+ subject.methods.should include('foobar')
33
+ stub(subject).foobar {:new_foobar}
34
+ end
35
+
36
+ describe "being bound" do
37
+ it "sets __rr__original_{method_name} to the original method" do
38
+ subject.__rr__original_foobar.should == :original_foobar
39
+ end
40
+
41
+ describe "being called" do
42
+ it "returns the return value of the block" do
43
+ subject.foobar.should == :new_foobar
44
+ end
45
+
46
+ it "does not call the original method" do
47
+ subject.foobar
48
+ subject.original_foobar_called.should be_nil
49
+ end
50
+ end
35
51
 
36
- context "when method does not exist on object" do
37
- before do
38
- @subject = Object.new
39
- @method_name = :foobar
40
- subject.methods.should_not include(method_name.to_s)
52
+ describe "being reset" do
53
+ before do
54
+ RR::Space.reset_double(subject, :foobar)
55
+ end
56
+
57
+ it "rebinds the original method" do
58
+ subject.foobar.should == :original_foobar
59
+ end
60
+
61
+ it "removes __rr__original_{method_name}" do
62
+ subject.should_not respond_to(:__rr__original_foobar)
63
+ end
64
+ end
65
+ end
41
66
  end
42
67
 
43
- context "when the subject descends from active record" do
68
+ context "when the subject does not respond to the injected method" do
44
69
  before do
45
- def subject.descends_from_active_record?
46
- true
70
+ @subject = Object.new
71
+ subject.should_not respond_to(:foobar)
72
+ subject.methods.should_not include('foobar')
73
+ stub(subject).foobar {:new_foobar}
74
+ end
75
+
76
+ it "does not set __rr__original_{method_name} to the original method" do
77
+ subject.should_not respond_to(:__rr__original_foobar)
78
+ end
79
+
80
+ describe "being called" do
81
+ it "calls the newly defined method" do
82
+ subject.foobar.should == :new_foobar
47
83
  end
48
- def subject.respond_to?(name)
49
- return true if name == :foobar
50
- super
84
+ end
85
+
86
+ describe "being reset" do
87
+ before do
88
+ RR::Space.reset_double(subject, :foobar)
51
89
  end
52
- def subject.method_missing(name, *args, &block)
53
- if name == :foobar
54
- def self.foobar
55
- end
56
- else
57
- super
58
- end
90
+
91
+ it "unsets the foobar method" do
92
+ subject.should_not respond_to(:foobar)
93
+ subject.methods.should_not include('foobar')
59
94
  end
60
- @double_injection = DoubleInjection.new(subject, method_name, (class << subject; self; end))
61
95
  end
62
-
63
- send("sets up object and method_name")
96
+ end
64
97
 
65
- example "object has the original method" do
66
- double_injection.object_has_original_method?.should be_true
98
+ context "when the subject redefines respond_to?" do
99
+ it "does not try to call the implementation" do
100
+ class << subject
101
+ def respond_to?(method_symbol, include_private = false)
102
+ method_symbol == :foobar
103
+ end
104
+ end
105
+ mock(@subject).foobar
106
+ @subject.foobar.should == nil
67
107
  end
68
108
  end
69
109
  end
70
110
 
71
- context "when method exists on object" do
72
- send("sets up object and method_name")
111
+ describe "mock/stub + proxy" do
112
+ context "when the subject responds to the injected method" do
113
+ context "when the subject has the method defined" do
114
+ describe "being bound" do
115
+ before do
116
+ @subject = Object.new
73
117
 
74
- before do
75
- @subject = Object.new
76
- @method_name = :to_s
77
- subject.methods.should include(method_name.to_s)
78
- @double_injection = DoubleInjection.new(subject, method_name, (class << subject; self; end))
79
- end
118
+ def subject.foobar
119
+ :original_foobar
120
+ end
121
+
122
+ subject.should respond_to(:foobar)
123
+ subject.methods.should include('foobar')
124
+ stub.proxy(subject).foobar {:new_foobar}
125
+ end
126
+
127
+ it "aliases the original method to __rr__original_{method_name}" do
128
+ subject.__rr__original_foobar.should == :original_foobar
129
+ end
130
+
131
+ it "replaces the original method with the new method" do
132
+ subject.foobar.should == :new_foobar
133
+ end
134
+
135
+ describe "being called" do
136
+ it "calls the original method first and sends it into the block" do
137
+ original_return_value = nil
138
+ stub.proxy(subject).foobar {|original_return_value| :new_foobar}
139
+ subject.foobar.should == :new_foobar
140
+ original_return_value.should == :original_foobar
141
+ end
142
+ end
143
+
144
+ describe "being reset" do
145
+ before do
146
+ RR::Space.reset_double(subject, :foobar)
147
+ end
148
+
149
+ it "rebinds the original method" do
150
+ subject.foobar.should == :original_foobar
151
+ end
152
+
153
+ it "removes __rr__original_{method_name}" do
154
+ subject.should_not respond_to(:__rr__original_foobar)
155
+ end
156
+ end
157
+ end
158
+ end
159
+
160
+ context "when the subject does not have the method defined" do
161
+ describe "being bound" do
162
+ context "when the subject has not been previously bound to" do
163
+ before do
164
+ @subject = Object.new
165
+ setup_subject
166
+
167
+ subject.should respond_to(:foobar)
168
+ stub.proxy(subject).foobar {:new_foobar}
169
+ end
170
+
171
+ def setup_subject
172
+ def subject.respond_to?(method_name)
173
+ if method_name.to_sym == :foobar
174
+ true
175
+ else
176
+ super
177
+ end
178
+ end
179
+ end
180
+
181
+ it "does not define __rr__original_{method_name}" do
182
+ subject.methods.should_not include("__rr__original_foobar")
183
+ end
184
+
185
+ context "when method is defined after being bound and before being called" do
186
+ def setup_subject
187
+ super
188
+ def subject.foobar
189
+ :original_foobar
190
+ end
191
+ end
192
+
193
+ describe "being called" do
194
+ it "defines __rr__original_{method_name} to be the lazily created method" do
195
+ subject.methods.should include("__rr__original_foobar")
196
+ subject.__rr__original_foobar.should == :original_foobar
197
+ end
198
+
199
+ it "calls the original method first and sends it into the block" do
200
+ original_return_value = nil
201
+ stub.proxy(subject).foobar {|original_return_value| :new_foobar}
202
+ subject.foobar.should == :new_foobar
203
+ original_return_value.should == :original_foobar
204
+ end
205
+ end
206
+
207
+ describe "being reset" do
208
+ before do
209
+ RR::Space.reset_double(subject, :foobar)
210
+ end
211
+
212
+ it "rebinds the original method" do
213
+ subject.foobar.should == :original_foobar
214
+ end
215
+
216
+ it "removes __rr__original_{method_name}" do
217
+ subject.should_not respond_to(:__rr__original_foobar)
218
+ end
219
+ end
220
+ end
221
+
222
+ context "when method is still not defined" do
223
+ context "when the method is lazily created" do
224
+ def setup_subject
225
+ super
226
+ def subject.method_missing(method_name, *args, &block)
227
+ if method_name.to_sym == :foobar
228
+ def self.foobar
229
+ :original_foobar
230
+ end
80
231
 
81
- it "has a original_method" do
82
- double_injection.object_has_original_method?.should be_true
232
+ foobar
233
+ else
234
+ super
235
+ end
236
+ end
237
+ end
238
+
239
+ describe "being called" do
240
+ it "defines __rr__original_{method_name} to be the lazily created method" do
241
+ subject.foobar
242
+ subject.methods.should include("__rr__original_foobar")
243
+ subject.__rr__original_foobar.should == :original_foobar
244
+ end
245
+
246
+ it "calls the lazily created method and returns the injected method return value" do
247
+ original_return_value = nil
248
+ stub.proxy(subject).foobar {|original_return_value| :new_foobar}
249
+ subject.foobar.should == :new_foobar
250
+ original_return_value.should == :original_foobar
251
+ end
252
+ end
253
+
254
+ describe "being reset" do
255
+ context "when reset before being called" do
256
+ before do
257
+ RR::Space.reset_double(subject, :foobar)
258
+ end
259
+
260
+ it "rebinds the original method" do
261
+ subject.foobar.should == :original_foobar
262
+ end
263
+
264
+ it "removes __rr__original_{method_name}" do
265
+ subject.should_not respond_to(:__rr__original_foobar)
266
+ end
267
+ end
268
+ end
269
+ end
270
+
271
+ context "when the method is not lazily created (handled in method_missing)" do
272
+ def setup_subject
273
+ super
274
+ def subject.method_missing(method_name, *args, &block)
275
+ if method_name.to_sym == :foobar
276
+ :original_foobar
277
+ else
278
+ super
279
+ end
280
+ end
281
+ end
282
+
283
+ describe "being called" do
284
+ it "does not define the __rr__original_{method_name}" do
285
+ subject.foobar
286
+ subject.methods.should_not include("__rr__original_foobar")
287
+ end
288
+
289
+ it "calls the lazily created method and returns the injected method return value" do
290
+ original_return_value = nil
291
+ stub.proxy(subject).foobar {|original_return_value| :new_foobar}
292
+ subject.foobar.should == :new_foobar
293
+ original_return_value.should == :original_foobar
294
+ end
295
+ end
296
+
297
+ describe "being reset" do
298
+ before do
299
+ RR::Space.reset_double(subject, :foobar)
300
+ end
301
+
302
+ it "rebinds the original method" do
303
+ subject.foobar.should == :original_foobar
304
+ end
305
+
306
+ it "removes __rr__original_{method_name}" do
307
+ subject.should_not respond_to(:__rr__original_foobar)
308
+ end
309
+ end
310
+ end
311
+ end
312
+ end
313
+
314
+ context "when the subject has been previously bound to" do
315
+ before do
316
+ @subject = Object.new
317
+ setup_subject
318
+
319
+ subject.should respond_to(:foobar)
320
+ stub.proxy(subject).baz {:new_baz}
321
+ stub.proxy(subject).foobar {:new_foobar}
322
+ end
323
+
324
+ def setup_subject
325
+ def subject.respond_to?(method_name)
326
+ if method_name.to_sym == :foobar || method_name.to_sym == :baz
327
+ true
328
+ else
329
+ super
330
+ end
331
+ end
332
+ end
333
+
334
+ it "does not define __rr__original_{method_name}" do
335
+ subject.methods.should_not include("__rr__original_foobar")
336
+ end
337
+
338
+ context "when method is defined after being bound and before being called" do
339
+ def setup_subject
340
+ super
341
+ def subject.foobar
342
+ :original_foobar
343
+ end
344
+ end
345
+
346
+ describe "being called" do
347
+ it "defines __rr__original_{method_name} to be the lazily created method" do
348
+ subject.methods.should include("__rr__original_foobar")
349
+ subject.__rr__original_foobar.should == :original_foobar
350
+ end
351
+
352
+ it "calls the original method first and sends it into the block" do
353
+ original_return_value = nil
354
+ stub.proxy(subject).foobar {|original_return_value| :new_foobar}
355
+ subject.foobar.should == :new_foobar
356
+ original_return_value.should == :original_foobar
357
+ end
358
+ end
359
+
360
+ describe "being reset" do
361
+ before do
362
+ RR::Space.reset_double(subject, :foobar)
363
+ end
364
+
365
+ it "rebinds the original method" do
366
+ subject.foobar.should == :original_foobar
367
+ end
368
+
369
+ it "removes __rr__original_{method_name}" do
370
+ subject.should_not respond_to(:__rr__original_foobar)
371
+ end
372
+ end
373
+ end
374
+
375
+ context "when method is still not defined" do
376
+ context "when the method is lazily created" do
377
+ def setup_subject
378
+ super
379
+ def subject.method_missing(method_name, *args, &block)
380
+ if method_name.to_sym == :foobar
381
+ def self.foobar
382
+ :original_foobar
383
+ end
384
+
385
+ foobar
386
+ else
387
+ super
388
+ end
389
+ end
390
+ end
391
+
392
+ describe "being called" do
393
+ it "defines __rr__original_{method_name} to be the lazily created method" do
394
+ subject.foobar
395
+ subject.methods.should include("__rr__original_foobar")
396
+ subject.__rr__original_foobar.should == :original_foobar
397
+ end
398
+
399
+ it "calls the lazily created method and returns the injected method return value" do
400
+ original_return_value = nil
401
+ stub.proxy(subject).foobar {|original_return_value| :new_foobar}
402
+ subject.foobar.should == :new_foobar
403
+ original_return_value.should == :original_foobar
404
+ end
405
+ end
406
+
407
+ describe "being reset" do
408
+ context "when reset before being called" do
409
+ before do
410
+ RR::Space.reset_double(subject, :foobar)
411
+ end
412
+
413
+ it "rebinds the original method" do
414
+ subject.foobar.should == :original_foobar
415
+ end
416
+
417
+ it "removes __rr__original_{method_name}" do
418
+ subject.should_not respond_to(:__rr__original_foobar)
419
+ end
420
+ end
421
+ end
422
+ end
423
+
424
+ context "when the method is not lazily created (handled in method_missing)" do
425
+ def setup_subject
426
+ super
427
+ def subject.method_missing(method_name, *args, &block)
428
+ if method_name.to_sym == :foobar
429
+ :original_foobar
430
+ else
431
+ super
432
+ end
433
+ end
434
+ end
435
+
436
+ describe "being called" do
437
+ it "does not define the __rr__original_{method_name}" do
438
+ subject.foobar
439
+ subject.methods.should_not include("__rr__original_foobar")
440
+ end
441
+
442
+ it "calls the lazily created method and returns the injected method return value" do
443
+ original_return_value = nil
444
+ stub.proxy(subject).foobar {|original_return_value| :new_foobar}
445
+ subject.foobar.should == :new_foobar
446
+ original_return_value.should == :original_foobar
447
+ end
448
+ end
449
+
450
+ describe "being reset" do
451
+ before do
452
+ RR::Space.reset_double(subject, :foobar)
453
+ end
454
+
455
+ it "rebinds the original method" do
456
+ subject.foobar.should == :original_foobar
457
+ end
458
+
459
+ it "removes __rr__original_{method_name}" do
460
+ subject.should_not respond_to(:__rr__original_foobar)
461
+ end
462
+ end
463
+ end
464
+ end
465
+ end
466
+ end
467
+ end
83
468
  end
84
- end
85
469
 
86
- context "when method_name is ==" do
87
- send("sets up object and method_name")
470
+ context "when the subject does not respond to the injected method" do
471
+ context "when the subject responds to the method via method_missing" do
472
+ describe "being bound" do
473
+ before do
474
+ @subject = Object.new
475
+ subject.should_not respond_to(:foobar)
476
+ subject.methods.should_not include('foobar')
477
+ class << subject
478
+ def method_missing(method_name, *args, &block)
479
+ if method_name == :foobar
480
+ :original_foobar
481
+ else
482
+ super
483
+ end
484
+ end
485
+ end
486
+ stub.proxy(subject).foobar {:new_foobar}
487
+ end
488
+
489
+ it "adds the method to the subject" do
490
+ subject.should respond_to(:foobar)
491
+ subject.methods.should include('foobar')
492
+ end
493
+
494
+ describe "being called" do
495
+ it "calls the original method first and sends it into the block" do
496
+ original_return_value = nil
497
+ stub.proxy(subject).foobar {|original_return_value| :new_foobar}
498
+ subject.foobar.should == :new_foobar
499
+ original_return_value.should == :original_foobar
500
+ end
501
+ end
502
+
503
+ describe "being reset" do
504
+ before do
505
+ RR::Space.reset_double(subject, :foobar)
506
+ end
507
+
508
+ it "unsets the foobar method" do
509
+ subject.should_not respond_to(:foobar)
510
+ subject.methods.should_not include('foobar')
511
+ end
512
+ end
513
+ end
514
+ end
515
+
516
+ context "when the subject would raise a NoMethodError when the method is called" do
517
+ describe "being bound" do
518
+ before do
519
+ @subject = Object.new
520
+ subject.should_not respond_to(:foobar)
521
+ subject.methods.should_not include('foobar')
522
+ stub.proxy(subject).foobar {:new_foobar}
523
+ end
524
+
525
+ it "adds the method to the subject" do
526
+ subject.should respond_to(:foobar)
527
+ subject.methods.should include('foobar')
528
+ end
529
+
530
+ describe "being called" do
531
+ it "raises a NoMethodError" do
532
+ lambda do
533
+ subject.foobar
534
+ end.should raise_error(NoMethodError)
535
+ end
536
+ end
537
+
538
+ describe "being reset" do
539
+ before do
540
+ RR::Space.reset_double(subject, :foobar)
541
+ end
88
542
 
89
- before do
90
- @subject = Object.new
91
- @method_name = '=='
92
- @double_injection = DoubleInjection.new(subject, method_name, (class << subject; self; end))
543
+ it "unsets the foobar method" do
544
+ subject.should_not respond_to(:foobar)
545
+ subject.methods.should_not include('foobar')
546
+ end
547
+ end
548
+ end
549
+ end
93
550
  end
94
551
  end
95
552
  end