rr 1.2.1 → 3.0.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.
- checksums.yaml +5 -5
- data/CHANGES.md +69 -0
- data/Gemfile +1 -8
- data/README.md +9 -13
- data/Rakefile +30 -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 +29 -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 +83 -25
- data/lib/rr/injections/method_missing_injection.rb +36 -9
- data/lib/rr/keyword_arguments.rb +21 -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
@@ -32,8 +32,14 @@ module RR
|
|
32
32
|
# end
|
33
33
|
class Mock < VerificationStrategy
|
34
34
|
protected
|
35
|
-
|
36
|
-
|
35
|
+
if KeywordArguments.fully_supported?
|
36
|
+
def do_call
|
37
|
+
definition.with(*args, **kwargs).once
|
38
|
+
end
|
39
|
+
else
|
40
|
+
def do_call
|
41
|
+
definition.with(*args).once
|
42
|
+
end
|
37
43
|
end
|
38
44
|
end
|
39
45
|
end
|
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)
|
@@ -126,14 +137,25 @@ module RR
|
|
126
137
|
id = BoundObjects.size
|
127
138
|
BoundObjects[id] = subject_class
|
128
139
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
140
|
+
if KeywordArguments.fully_supported? && KeywordArguments.accept_kwargs?(subject_class, method_name)
|
141
|
+
subject_class.class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
142
|
+
def #{method_name}(*args, **kwargs, &block)
|
143
|
+
::RR::Injections::DoubleInjection::BoundObjects[#{id}].class_eval do
|
144
|
+
remove_method(:#{method_name})
|
145
|
+
end
|
146
|
+
method_missing(:#{method_name}, *args, **kwargs, &block)
|
133
147
|
end
|
134
|
-
|
135
|
-
|
136
|
-
|
148
|
+
RUBY
|
149
|
+
else
|
150
|
+
subject_class.class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
151
|
+
def #{method_name}(*args, &block)
|
152
|
+
::RR::Injections::DoubleInjection::BoundObjects[#{id}].class_eval do
|
153
|
+
remove_method(:#{method_name})
|
154
|
+
end
|
155
|
+
method_missing(:#{method_name}, *args, &block)
|
156
|
+
end
|
157
|
+
RUBY
|
158
|
+
end
|
137
159
|
self
|
138
160
|
end
|
139
161
|
|
@@ -141,13 +163,37 @@ module RR
|
|
141
163
|
id = BoundObjects.size
|
142
164
|
BoundObjects[id] = subject_class
|
143
165
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
166
|
+
if KeywordArguments.fully_supported? && KeywordArguments.accept_kwargs?(subject_class, method_name)
|
167
|
+
subject_class.class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
168
|
+
def #{method_name}(*args, **kwargs, &block)
|
169
|
+
arguments = MethodArguments.new(args, kwargs, block)
|
170
|
+
obj = ::RR::Injections::DoubleInjection::BoundObjects[#{id}]
|
171
|
+
::RR::Injections::DoubleInjection.dispatch_method(
|
172
|
+
self,
|
173
|
+
obj,
|
174
|
+
:#{method_name},
|
175
|
+
arguments.arguments,
|
176
|
+
arguments.keyword_arguments,
|
177
|
+
arguments.block
|
178
|
+
)
|
179
|
+
end
|
180
|
+
RUBY
|
181
|
+
else
|
182
|
+
subject_class.class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
183
|
+
def #{method_name}(*args, &block)
|
184
|
+
arguments = MethodArguments.new(args, {}, block)
|
185
|
+
obj = ::RR::Injections::DoubleInjection::BoundObjects[#{id}]
|
186
|
+
::RR::Injections::DoubleInjection.dispatch_method(
|
187
|
+
self,
|
188
|
+
obj,
|
189
|
+
:#{method_name},
|
190
|
+
arguments.arguments,
|
191
|
+
arguments.keyword_arguments,
|
192
|
+
arguments.block
|
193
|
+
)
|
194
|
+
end
|
195
|
+
RUBY
|
196
|
+
end
|
151
197
|
self
|
152
198
|
end
|
153
199
|
|
@@ -175,17 +221,29 @@ module RR
|
|
175
221
|
end
|
176
222
|
end
|
177
223
|
|
178
|
-
def dispatch_method(subject, args, block)
|
224
|
+
def dispatch_method(subject, args, kwargs, block)
|
179
225
|
if @dispatch_method_delegates_to_dispatch_original_method
|
180
|
-
dispatch_original_method(subject, args, block)
|
226
|
+
dispatch_original_method(subject, args, kwargs, block)
|
181
227
|
else
|
182
|
-
dispatch = MethodDispatches::MethodDispatch.new(
|
228
|
+
dispatch = MethodDispatches::MethodDispatch.new(
|
229
|
+
self,
|
230
|
+
subject,
|
231
|
+
args,
|
232
|
+
kwargs,
|
233
|
+
block
|
234
|
+
)
|
183
235
|
dispatch.call
|
184
236
|
end
|
185
237
|
end
|
186
238
|
|
187
|
-
def dispatch_original_method(subject, args, block)
|
188
|
-
dispatch = MethodDispatches::MethodDispatch.new(
|
239
|
+
def dispatch_original_method(subject, args, kwargs, block)
|
240
|
+
dispatch = MethodDispatches::MethodDispatch.new(
|
241
|
+
self,
|
242
|
+
subject,
|
243
|
+
args,
|
244
|
+
kwargs,
|
245
|
+
block
|
246
|
+
)
|
189
247
|
dispatch.call_original_method
|
190
248
|
end
|
191
249
|
|
@@ -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
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module RR
|
2
|
+
module KeywordArguments
|
3
|
+
class << self
|
4
|
+
if (RUBY_VERSION <=> "2.7.0") >= 0
|
5
|
+
def fully_supported?
|
6
|
+
true
|
7
|
+
end
|
8
|
+
else
|
9
|
+
def fully_supported?
|
10
|
+
false
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def accept_kwargs?(subject_class, method_name)
|
15
|
+
subject_class.instance_method(method_name).parameters.any? { |t, _| t == :key || t == :keyrest }
|
16
|
+
rescue NameError
|
17
|
+
true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -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? && !kwargs.empty?
|
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? && !kwargs.empty?
|
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
|