rr 0.10.2 → 0.10.4

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.
@@ -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