rr 1.2.1 → 3.0.0
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.
- checksums.yaml +5 -5
- data/CHANGES.md +25 -0
- data/Gemfile +1 -8
- data/README.md +8 -13
- data/Rakefile +16 -6
- data/lib/rr/class_instance_method_defined.rb +1 -1
- data/lib/rr/double.rb +28 -10
- data/lib/rr/double_definitions/double_definition.rb +39 -16
- data/lib/rr/double_definitions/double_definition_create.rb +5 -5
- data/lib/rr/double_definitions/double_definition_create_blank_slate.rb +10 -4
- data/lib/rr/double_definitions/strategies/strategy.rb +27 -8
- data/lib/rr/double_definitions/strategies/verification/mock.rb +8 -2
- data/lib/rr/double_matches.rb +4 -3
- data/lib/rr/expectations/any_argument_expectation.rb +4 -4
- data/lib/rr/expectations/argument_equality_expectation.rb +43 -5
- data/lib/rr/injections/double_injection.rb +65 -18
- data/lib/rr/injections/method_missing_injection.rb +36 -9
- data/lib/rr/keyword_arguments.rb +15 -0
- data/lib/rr/method_dispatches/base_method_dispatch.rb +22 -5
- data/lib/rr/method_dispatches/method_dispatch.rb +21 -10
- data/lib/rr/method_dispatches/method_missing_dispatch.rb +14 -5
- data/lib/rr/recorded_call.rb +9 -3
- data/lib/rr/recorded_calls.rb +17 -7
- data/lib/rr/space.rb +15 -5
- data/lib/rr/version.rb +1 -1
- data/lib/rr/without_autohook.rb +2 -1
- data/rr.gemspec +5 -0
- data/spec/suites.yml +1 -15
- data/spec/suites/rspec_2/unit/double_definitions/double_definition_create_spec.rb +18 -18
- data/spec/suites/rspec_2/unit/expectations/any_argument_expectation_spec.rb +9 -9
- data/spec/suites/rspec_2/unit/expectations/argument_equality_expectation_spec.rb +21 -21
- data/spec/suites/rspec_2/unit/expectations/boolean_argument_equality_expectation_spec.rb +4 -4
- data/spec/suites/rspec_2/unit/expectations/hash_including_argument_equality_expectation_spec.rb +31 -21
- data/spec/suites/rspec_2/unit/space_spec.rb +4 -3
- metadata +61 -10
- data/lib/rr/proc_from_block.rb +0 -11
- data/spec/suites/rspec_2/unit/proc_from_block_spec.rb +0 -14
- data/spec/suites/rspec_2_rails_4/integration/astc_rails_4_spec.rb +0 -142
- data/spec/suites/rspec_2_rails_4/integration/minitest_4_rails_4_spec.rb +0 -149
- data/spec/suites/rspec_2_rails_4/spec_helper.rb +0 -3
data/lib/rr/double_matches.rb
CHANGED
@@ -15,9 +15,10 @@ module RR
|
|
15
15
|
@wildcard_non_terminal_doubles_to_attempt = []
|
16
16
|
end
|
17
17
|
|
18
|
-
def find_all_matches(args)
|
18
|
+
def find_all_matches(args, kwargs)
|
19
|
+
kwargs ||= {}
|
19
20
|
@doubles.each do |double|
|
20
|
-
if double.exact_match?(
|
21
|
+
if double.exact_match?(args, kwargs)
|
21
22
|
matching_doubles << double
|
22
23
|
if double.attempt?
|
23
24
|
if double.terminal?
|
@@ -26,7 +27,7 @@ module RR
|
|
26
27
|
exact_non_terminal_doubles_to_attempt << double
|
27
28
|
end
|
28
29
|
end
|
29
|
-
elsif double.wildcard_match?(
|
30
|
+
elsif double.wildcard_match?(args, kwargs)
|
30
31
|
matching_doubles << double
|
31
32
|
if double.attempt?
|
32
33
|
if double.terminal?
|
@@ -2,14 +2,14 @@ module RR
|
|
2
2
|
module Expectations
|
3
3
|
class AnyArgumentExpectation < ArgumentEqualityExpectation #:nodoc:
|
4
4
|
def initialize
|
5
|
-
super
|
5
|
+
super([], {})
|
6
6
|
end
|
7
7
|
|
8
|
-
def exact_match?(
|
8
|
+
def exact_match?(arguments, keyword_arguments)
|
9
9
|
false
|
10
10
|
end
|
11
11
|
|
12
|
-
def wildcard_match?(
|
12
|
+
def wildcard_match?(arguments, keyword_arguments)
|
13
13
|
true
|
14
14
|
end
|
15
15
|
|
@@ -18,4 +18,4 @@ module RR
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
end
|
21
|
-
end
|
21
|
+
end
|
@@ -10,20 +10,37 @@ module RR
|
|
10
10
|
end
|
11
11
|
|
12
12
|
attr_reader :expected_arguments
|
13
|
+
attr_reader :expected_keyword_arguments
|
13
14
|
|
14
|
-
def initialize(
|
15
|
+
def initialize(expected_arguments,
|
16
|
+
expected_keyword_arguments)
|
15
17
|
@expected_arguments = expected_arguments
|
18
|
+
@expected_keyword_arguments = expected_keyword_arguments
|
16
19
|
end
|
17
20
|
|
18
|
-
def exact_match?(
|
21
|
+
def exact_match?(arguments, keyword_arguments)
|
19
22
|
return false unless arguments.length == expected_arguments.length
|
20
23
|
arguments.each_with_index do |arg, index|
|
21
|
-
|
24
|
+
expected_arg = expected_arguments[index]
|
25
|
+
return false unless self.class.recursive_safe_eq(expected_arg, arg)
|
26
|
+
end
|
27
|
+
keywords = keyword_arguments.keys
|
28
|
+
expected_keywords = expected_keyword_arguments.keys
|
29
|
+
unless keywords.length == expected_keywords.length
|
30
|
+
return false
|
31
|
+
end
|
32
|
+
keywords.each do |keyword|
|
33
|
+
keyword_argument = keyword_arguments[keyword]
|
34
|
+
expected_keyword_argument = expected_keyword_arguments[keyword]
|
35
|
+
unless self.class.recursive_safe_eq(expected_keyword_argument,
|
36
|
+
keyword_argument)
|
37
|
+
return false
|
38
|
+
end
|
22
39
|
end
|
23
40
|
true
|
24
41
|
end
|
25
42
|
|
26
|
-
def wildcard_match?(
|
43
|
+
def wildcard_match?(arguments, keyword_arguments)
|
27
44
|
return false unless arguments.length == expected_arguments.length
|
28
45
|
arguments.each_with_index do |arg, index|
|
29
46
|
expected_argument = expected_arguments[index]
|
@@ -33,11 +50,32 @@ module RR
|
|
33
50
|
return false unless self.class.recursive_safe_eq(expected_argument, arg)
|
34
51
|
end
|
35
52
|
end
|
53
|
+
keywords = keyword_arguments.keys
|
54
|
+
expected_keywords = expected_keyword_arguments.keys
|
55
|
+
unless keywords.length == expected_keywords.length
|
56
|
+
return false
|
57
|
+
end
|
58
|
+
keywords.each do |keyword|
|
59
|
+
keyword_argument = keyword_arguments[keyword]
|
60
|
+
expected_keyword_argument = expected_keyword_arguments[keyword]
|
61
|
+
if expected_keyword_argument.respond_to?(:wildcard_match?)
|
62
|
+
unless expected_keyword_argument.wildcard_match?(keyword_argument)
|
63
|
+
return false
|
64
|
+
end
|
65
|
+
else
|
66
|
+
unless self.class.recursive_safe_eq(expected_keyword_argument,
|
67
|
+
keyword_argument)
|
68
|
+
return false
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
36
72
|
true
|
37
73
|
end
|
38
74
|
|
39
75
|
def ==(other)
|
40
|
-
|
76
|
+
other.is_a?(self.class) and
|
77
|
+
expected_arguments == other.expected_arguments and
|
78
|
+
expected_keyword_arguments == other.expected_keyword_arguments
|
41
79
|
end
|
42
80
|
end
|
43
81
|
end
|
@@ -31,15 +31,20 @@ module RR
|
|
31
31
|
exists?((class << subject; self; end), method_name)
|
32
32
|
end
|
33
33
|
|
34
|
-
def dispatch_method(subject,
|
34
|
+
def dispatch_method(subject,
|
35
|
+
subject_class,
|
36
|
+
method_name,
|
37
|
+
arguments,
|
38
|
+
keyword_arguments,
|
39
|
+
block)
|
35
40
|
subject_eigenclass = (class << subject; self; end)
|
36
41
|
if (
|
37
42
|
exists?(subject_class, method_name) &&
|
38
|
-
(subject_class == subject_eigenclass) || !subject.is_a?(Class)
|
43
|
+
((subject_class == subject_eigenclass) || !subject.is_a?(Class))
|
39
44
|
)
|
40
|
-
find(subject_class, method_name.to_sym).dispatch_method(subject, arguments, block)
|
45
|
+
find(subject_class, method_name.to_sym).dispatch_method(subject, arguments, keyword_arguments, block)
|
41
46
|
else
|
42
|
-
new(subject_class, method_name.to_sym).dispatch_original_method(subject, arguments, block)
|
47
|
+
new(subject_class, method_name.to_sym).dispatch_original_method(subject, arguments, keyword_arguments, block)
|
43
48
|
end
|
44
49
|
end
|
45
50
|
|
@@ -91,7 +96,9 @@ module RR
|
|
91
96
|
|
92
97
|
attr_reader :subject_class, :method_name, :doubles
|
93
98
|
|
94
|
-
MethodArguments = Struct.new(:arguments,
|
99
|
+
MethodArguments = Struct.new(:arguments,
|
100
|
+
:keyword_arguments,
|
101
|
+
:block)
|
95
102
|
|
96
103
|
def initialize(subject_class, method_name)
|
97
104
|
@subject_class = subject_class
|
@@ -111,7 +118,11 @@ module RR
|
|
111
118
|
# is called.
|
112
119
|
def bind
|
113
120
|
if subject_has_method_defined?(method_name)
|
114
|
-
|
121
|
+
if subject_has_original_method?
|
122
|
+
bind_method
|
123
|
+
else
|
124
|
+
bind_method_with_alias
|
125
|
+
end
|
115
126
|
else
|
116
127
|
Injections::MethodMissingInjection.find_or_create(subject_class)
|
117
128
|
Injections::SingletonMethodAddedInjection.find_or_create(subject_class)
|
@@ -141,13 +152,37 @@ module RR
|
|
141
152
|
id = BoundObjects.size
|
142
153
|
BoundObjects[id] = subject_class
|
143
154
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
155
|
+
if KeywordArguments.fully_supported?
|
156
|
+
subject_class.class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
157
|
+
def #{method_name}(*args, **kwargs, &block)
|
158
|
+
arguments = MethodArguments.new(args, kwargs, block)
|
159
|
+
obj = ::RR::Injections::DoubleInjection::BoundObjects[#{id}]
|
160
|
+
::RR::Injections::DoubleInjection.dispatch_method(
|
161
|
+
self,
|
162
|
+
obj,
|
163
|
+
:#{method_name},
|
164
|
+
arguments.arguments,
|
165
|
+
arguments.keyword_arguments,
|
166
|
+
arguments.block
|
167
|
+
)
|
168
|
+
end
|
169
|
+
RUBY
|
170
|
+
else
|
171
|
+
subject_class.class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
172
|
+
def #{method_name}(*args, &block)
|
173
|
+
arguments = MethodArguments.new(args, {}, block)
|
174
|
+
obj = ::RR::Injections::DoubleInjection::BoundObjects[#{id}]
|
175
|
+
::RR::Injections::DoubleInjection.dispatch_method(
|
176
|
+
self,
|
177
|
+
obj,
|
178
|
+
:#{method_name},
|
179
|
+
arguments.arguments,
|
180
|
+
arguments.keyword_arguments,
|
181
|
+
arguments.block
|
182
|
+
)
|
183
|
+
end
|
184
|
+
RUBY
|
185
|
+
end
|
151
186
|
self
|
152
187
|
end
|
153
188
|
|
@@ -175,17 +210,29 @@ module RR
|
|
175
210
|
end
|
176
211
|
end
|
177
212
|
|
178
|
-
def dispatch_method(subject, args, block)
|
213
|
+
def dispatch_method(subject, args, kwargs, block)
|
179
214
|
if @dispatch_method_delegates_to_dispatch_original_method
|
180
|
-
dispatch_original_method(subject, args, block)
|
215
|
+
dispatch_original_method(subject, args, kwargs, block)
|
181
216
|
else
|
182
|
-
dispatch = MethodDispatches::MethodDispatch.new(
|
217
|
+
dispatch = MethodDispatches::MethodDispatch.new(
|
218
|
+
self,
|
219
|
+
subject,
|
220
|
+
args,
|
221
|
+
kwargs,
|
222
|
+
block
|
223
|
+
)
|
183
224
|
dispatch.call
|
184
225
|
end
|
185
226
|
end
|
186
227
|
|
187
|
-
def dispatch_original_method(subject, args, block)
|
188
|
-
dispatch = MethodDispatches::MethodDispatch.new(
|
228
|
+
def dispatch_original_method(subject, args, kwargs, block)
|
229
|
+
dispatch = MethodDispatches::MethodDispatch.new(
|
230
|
+
self,
|
231
|
+
subject,
|
232
|
+
args,
|
233
|
+
kwargs,
|
234
|
+
block
|
235
|
+
)
|
189
236
|
dispatch.call_original_method
|
190
237
|
end
|
191
238
|
|
@@ -71,16 +71,43 @@ module RR
|
|
71
71
|
id = BoundObjects.size
|
72
72
|
BoundObjects[id] = subject_class
|
73
73
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
74
|
+
if KeywordArguments.fully_supported?
|
75
|
+
subject_class.class_eval((<<-METHOD), __FILE__, __LINE__ + 1)
|
76
|
+
def method_missing(method_name, *args, **kwargs, &block)
|
77
|
+
if respond_to_missing?(method_name, true)
|
78
|
+
super(method_name, *args, **kwargs, &block)
|
79
|
+
else
|
80
|
+
obj = ::RR::Injections::MethodMissingInjection::BoundObjects[#{id}]
|
81
|
+
MethodDispatches::MethodMissingDispatch.new(
|
82
|
+
self,
|
83
|
+
obj,
|
84
|
+
method_name,
|
85
|
+
args,
|
86
|
+
kwargs,
|
87
|
+
block
|
88
|
+
).call
|
89
|
+
end
|
81
90
|
end
|
82
|
-
|
83
|
-
|
91
|
+
METHOD
|
92
|
+
else
|
93
|
+
subject_class.class_eval((<<-METHOD), __FILE__, __LINE__ + 1)
|
94
|
+
def method_missing(method_name, *args, &block)
|
95
|
+
if respond_to_missing?(method_name, true)
|
96
|
+
super(method_name, *args, &block)
|
97
|
+
else
|
98
|
+
obj = ::RR::Injections::MethodMissingInjection::BoundObjects[#{id}]
|
99
|
+
MethodDispatches::MethodMissingDispatch.new(
|
100
|
+
self,
|
101
|
+
obj,
|
102
|
+
method_name,
|
103
|
+
args,
|
104
|
+
{},
|
105
|
+
block
|
106
|
+
).call
|
107
|
+
end
|
108
|
+
end
|
109
|
+
METHOD
|
110
|
+
end
|
84
111
|
end
|
85
112
|
|
86
113
|
def original_method_alias_name
|
@@ -4,7 +4,7 @@ module RR
|
|
4
4
|
extend Forwardable
|
5
5
|
include Space::Reader
|
6
6
|
|
7
|
-
attr_reader :args, :block, :double
|
7
|
+
attr_reader :args, :kwargs, :block, :double
|
8
8
|
|
9
9
|
def call
|
10
10
|
raise NotImplementedError
|
@@ -12,7 +12,7 @@ module RR
|
|
12
12
|
|
13
13
|
protected
|
14
14
|
def find_double_to_attempt
|
15
|
-
matches = DoubleMatches.new(doubles).find_all_matches(args)
|
15
|
+
matches = DoubleMatches.new(doubles).find_all_matches(args, kwargs)
|
16
16
|
|
17
17
|
unless matches.exact_terminal_doubles_to_attempt.empty?
|
18
18
|
return matches.exact_terminal_doubles_to_attempt.first
|
@@ -47,8 +47,25 @@ module RR
|
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
|
-
|
51
|
-
|
50
|
+
if KeywordArguments.fully_supported?
|
51
|
+
def call_original_method_missing
|
52
|
+
subject.__send__(
|
53
|
+
MethodMissingDispatch.original_method_missing_alias_name,
|
54
|
+
method_name,
|
55
|
+
*args,
|
56
|
+
**kwargs,
|
57
|
+
&block
|
58
|
+
)
|
59
|
+
end
|
60
|
+
else
|
61
|
+
def call_original_method_missing
|
62
|
+
subject.__send__(
|
63
|
+
MethodMissingDispatch.original_method_missing_alias_name,
|
64
|
+
method_name,
|
65
|
+
*args,
|
66
|
+
&block
|
67
|
+
)
|
68
|
+
end
|
52
69
|
end
|
53
70
|
|
54
71
|
def implementation_is_original_method?
|
@@ -70,7 +87,7 @@ module RR
|
|
70
87
|
message =
|
71
88
|
"On subject #{subject},\n" <<
|
72
89
|
"unexpected method invocation:\n" <<
|
73
|
-
" #{Double.formatted_name(method_name, args)}\n" <<
|
90
|
+
" #{Double.formatted_name(method_name, args, kwargs)}\n" <<
|
74
91
|
"expected invocations:\n" <<
|
75
92
|
Double.list_message_part(doubles)
|
76
93
|
raise RR::Errors.build_error(:DoubleNotFoundError, message)
|
@@ -3,15 +3,19 @@ module RR
|
|
3
3
|
class MethodDispatch < BaseMethodDispatch
|
4
4
|
attr_reader :double_injection, :subject
|
5
5
|
|
6
|
-
def initialize(double_injection, subject, args, block)
|
7
|
-
@double_injection
|
6
|
+
def initialize(double_injection, subject, args, kwargs, block)
|
7
|
+
@double_injection = double_injection
|
8
|
+
@subject = subject
|
9
|
+
@args = args
|
10
|
+
@kwargs = kwargs
|
11
|
+
@block = block
|
8
12
|
@double = find_double_to_attempt
|
9
13
|
end
|
10
14
|
|
11
15
|
def call
|
12
|
-
space.record_call(subject, method_name, args, block)
|
16
|
+
space.record_call(subject, method_name, args, kwargs, block)
|
13
17
|
if double
|
14
|
-
double.method_call(args)
|
18
|
+
double.method_call(args, kwargs)
|
15
19
|
call_yields
|
16
20
|
return_value_1 = call_implementation
|
17
21
|
return_value_2 = extract_subject_from_return_value(return_value_1)
|
@@ -27,11 +31,19 @@ module RR
|
|
27
31
|
|
28
32
|
def call_original_method
|
29
33
|
if subject_has_original_method?
|
30
|
-
|
34
|
+
if KeywordArguments.fully_supported?
|
35
|
+
subject.__send__(original_method_alias_name, *args, **kwargs, &block)
|
36
|
+
else
|
37
|
+
subject.__send__(original_method_alias_name, *args, &block)
|
38
|
+
end
|
31
39
|
elsif subject_has_original_method_missing?
|
32
40
|
call_original_method_missing
|
33
41
|
else
|
34
|
-
|
42
|
+
if KeywordArguments.fully_supported?
|
43
|
+
subject.__send__(:method_missing, method_name, *args, **kwargs, &block)
|
44
|
+
else
|
45
|
+
subject.__send__(:method_missing, method_name, *args, &block)
|
46
|
+
end
|
35
47
|
end
|
36
48
|
end
|
37
49
|
|
@@ -41,11 +53,10 @@ module RR
|
|
41
53
|
call_original_method
|
42
54
|
else
|
43
55
|
if implementation
|
44
|
-
if
|
45
|
-
implementation.call(*args, &block)
|
56
|
+
if KeywordArguments.fully_supported?
|
57
|
+
implementation.call(*args, **kwargs, &block)
|
46
58
|
else
|
47
|
-
|
48
|
-
implementation.call(*call_args)
|
59
|
+
implementation.call(*args, &block)
|
49
60
|
end
|
50
61
|
else
|
51
62
|
nil
|
@@ -9,8 +9,13 @@ module RR
|
|
9
9
|
|
10
10
|
attr_reader :subject, :subject_class, :method_name
|
11
11
|
|
12
|
-
def initialize(subject, subject_class, method_name, args, block)
|
13
|
-
@subject
|
12
|
+
def initialize(subject, subject_class, method_name, args, kwargs, block)
|
13
|
+
@subject = subject
|
14
|
+
@subject_class = subject_class
|
15
|
+
@method_name = method_name
|
16
|
+
@args = args
|
17
|
+
@kwargs = kwargs
|
18
|
+
@block = block
|
14
19
|
end
|
15
20
|
|
16
21
|
def call
|
@@ -40,14 +45,18 @@ module RR
|
|
40
45
|
protected
|
41
46
|
def call_implementation
|
42
47
|
if implementation_is_original_method?
|
43
|
-
space.record_call(subject, method_name, args, block)
|
44
|
-
double.method_call(args)
|
48
|
+
space.record_call(subject, method_name, args, kwargs, block)
|
49
|
+
double.method_call(args, kwargs)
|
45
50
|
call_original_method
|
46
51
|
else
|
47
52
|
if double_injection = Injections::DoubleInjection.find(subject_class, method_name)
|
48
53
|
double_injection.bind_method
|
49
54
|
# The DoubleInjection takes care of calling double.method_call
|
50
|
-
|
55
|
+
if KeywordArguments.fully_supported?
|
56
|
+
subject.__send__(method_name, *args, **kwargs, &block)
|
57
|
+
else
|
58
|
+
subject.__send__(method_name, *args, &block)
|
59
|
+
end
|
51
60
|
else
|
52
61
|
nil
|
53
62
|
end
|