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.
- data/CHANGES +3 -0
- data/README.rdoc +1 -0
- data/Rakefile +5 -1
- data/VERSION.yml +1 -1
- data/lib/rr.rb +8 -1
- data/lib/rr/double.rb +18 -78
- data/lib/rr/double_definitions/double_definition_creator_proxy.rb +1 -1
- data/lib/rr/hash_with_object_id_key.rb +1 -3
- data/lib/rr/injections/double_injection.rb +119 -0
- data/lib/rr/injections/injection.rb +22 -0
- data/lib/rr/injections/method_missing_injection.rb +62 -0
- data/lib/rr/injections/singleton_method_added_injection.rb +58 -0
- data/lib/rr/method_dispatches/base_method_dispatch.rb +84 -0
- data/lib/rr/method_dispatches/method_dispatch.rb +58 -0
- data/lib/rr/method_dispatches/method_missing_dispatch.rb +59 -0
- data/lib/rr/space.rb +44 -4
- data/lib/rr/spy_verification_proxy.rb +1 -1
- data/spec/rr/double_injection/double_injection_spec.rb +523 -66
- data/spec/rr/double_injection/double_injection_verify_spec.rb +3 -3
- data/spec/rr/double_spec.rb +18 -18
- data/spec/rr/space/hash_with_object_id_key_spec.rb +1 -1
- data/spec/rr/space/space_spec.rb +250 -64
- data/spec/spec_helper.rb +5 -0
- metadata +9 -7
- data/lib/rr/double_injection.rb +0 -136
- data/spec/rr/double_injection/double_injection_bind_spec.rb +0 -99
- data/spec/rr/double_injection/double_injection_dispatching_spec.rb +0 -244
- data/spec/rr/double_injection/double_injection_has_original_method_spec.rb +0 -58
- data/spec/rr/double_injection/double_injection_reset_spec.rb +0 -72
@@ -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
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
23
|
-
end
|
21
|
+
def foobar
|
22
|
+
@original_foobar_called = true
|
23
|
+
:original_foobar
|
24
|
+
end
|
25
|
+
end
|
24
26
|
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
34
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
68
|
+
context "when the subject does not respond to the injected method" do
|
44
69
|
before do
|
45
|
-
|
46
|
-
|
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
|
-
|
49
|
-
|
50
|
-
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "being reset" do
|
87
|
+
before do
|
88
|
+
RR::Space.reset_double(subject, :foobar)
|
51
89
|
end
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
66
|
-
|
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
|
-
|
72
|
-
|
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
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
-
|
82
|
-
|
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
|
-
|
87
|
-
|
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
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
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
|