rr 0.3.3 → 0.3.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 +5 -0
- data/Rakefile +1 -1
- data/examples/rr/double/double_register_scenario_example.rb +1 -1
- data/examples/rr/double/double_verify_example.rb +1 -1
- data/examples/rr/expectations/times_called_expectation/times_called_expectation_example.rb +0 -5
- data/examples/rr/expectations/times_called_expectation/times_called_expectation_integer_example.rb +2 -1
- data/examples/rr/expectations/times_called_expectation/times_called_expectation_proc_example.rb +2 -2
- data/examples/rr/expectations/times_called_expectation/times_called_expectation_range_example.rb +2 -1
- data/examples/rr/extensions/instance_methods_creator_example.rb +54 -9
- data/examples/rr/scenario_creator_example.rb +32 -42
- data/examples/rr/scenario_definition_example.rb +503 -0
- data/examples/rr/scenario_example.rb +48 -32
- data/examples/rr/scenario_method_proxy_example.rb +2 -2
- data/examples/rr/space/space_verify_example.rb +3 -3
- data/lib/rr.rb +4 -0
- data/lib/rr/expectations/times_called_expectation.rb +4 -5
- data/lib/rr/extensions/instance_methods.rb +16 -0
- data/lib/rr/scenario.rb +60 -82
- data/lib/rr/scenario_creator.rb +67 -56
- data/lib/rr/scenario_definition.rb +277 -0
- data/lib/rr/scenario_definition_builder.rb +43 -0
- data/lib/rr/space.rb +7 -2
- metadata +5 -2
data/lib/rr/scenario_creator.rb
CHANGED
@@ -9,7 +9,7 @@ module RR
|
|
9
9
|
class ScenarioCreator
|
10
10
|
NO_SUBJECT_ARG = Object.new
|
11
11
|
|
12
|
-
attr_reader :space
|
12
|
+
attr_reader :space
|
13
13
|
include Errors
|
14
14
|
|
15
15
|
def initialize(space)
|
@@ -17,26 +17,9 @@ module RR
|
|
17
17
|
@strategy = nil
|
18
18
|
@probe = false
|
19
19
|
@instance_of = nil
|
20
|
+
@instance_of_method_name = nil
|
20
21
|
end
|
21
22
|
|
22
|
-
def create!(subject, method_name, *args, &handler)
|
23
|
-
@subject = subject
|
24
|
-
@method_name = method_name
|
25
|
-
@args = args
|
26
|
-
@handler = handler
|
27
|
-
@double = @space.double(@subject, method_name)
|
28
|
-
@scenario = @space.scenario(@double)
|
29
|
-
transform!
|
30
|
-
@scenario
|
31
|
-
end
|
32
|
-
|
33
|
-
# def instance_of(subject=NO_SUBJECT_ARG, method_name=nil, &definition)
|
34
|
-
# return self if subject === NO_SUBJECT_ARG
|
35
|
-
# raise ArgumentError, "instance_of only accepts class objects" unless subject.is_a?(Class)
|
36
|
-
# @instance_of = true
|
37
|
-
# RR::Space.scenario_method_proxy(self, subject, method_name, &definition)
|
38
|
-
# end
|
39
|
-
|
40
23
|
# This method sets the Scenario to have a mock strategy. A mock strategy
|
41
24
|
# sets the default state of the Scenario to expect the method call
|
42
25
|
# with arguments exactly one time. The Scenario's expectations can be
|
@@ -184,53 +167,81 @@ module RR
|
|
184
167
|
return self if subject.__id__ === NO_SUBJECT_ARG.__id__
|
185
168
|
RR::Space.scenario_method_proxy(self, subject, method_name, &definition)
|
186
169
|
end
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
170
|
+
|
171
|
+
# Calling instance_of will cause all instances of the passed in Class
|
172
|
+
# to have the Scenario defined.
|
173
|
+
#
|
174
|
+
# The following example mocks all User's valid? method and return false.
|
175
|
+
# mock.instance_of(User).valid? {false}
|
176
|
+
#
|
177
|
+
# The following example mocks and probes User#projects and returns the
|
178
|
+
# first 3 projects.
|
179
|
+
# mock.instance_of(User).projects do |projects|
|
180
|
+
# projects[0..2]
|
181
|
+
# end
|
182
|
+
def instance_of(subject=NO_SUBJECT_ARG, method_name=nil, &definition)
|
183
|
+
@instance_of = true
|
184
|
+
return self if subject === NO_SUBJECT_ARG
|
185
|
+
raise ArgumentError, "instance_of only accepts class objects" unless subject.is_a?(Class)
|
186
|
+
RR::Space.scenario_method_proxy(self, subject, method_name, &definition)
|
187
|
+
end
|
188
|
+
|
189
|
+
def create!(subject, method_name, *args, &handler)
|
190
|
+
@args = args
|
191
|
+
@handler = handler
|
192
|
+
if @instance_of
|
193
|
+
setup_class_probing_instances(subject, method_name)
|
199
194
|
else
|
200
|
-
|
195
|
+
setup_scenario(subject, method_name)
|
201
196
|
end
|
197
|
+
transform!
|
198
|
+
@definition
|
202
199
|
end
|
203
|
-
|
204
|
-
|
205
|
-
|
200
|
+
|
201
|
+
protected
|
202
|
+
def setup_scenario(subject, method_name)
|
203
|
+
@double = @space.double(subject, method_name)
|
204
|
+
@scenario = @space.scenario(@double)
|
205
|
+
@definition = @scenario.definition
|
206
206
|
end
|
207
207
|
|
208
|
-
def
|
209
|
-
@
|
210
|
-
|
211
|
-
end
|
208
|
+
def setup_class_probing_instances(subject, method_name)
|
209
|
+
class_double = @space.double(subject, :new)
|
210
|
+
class_scenario = @space.scenario(class_double)
|
212
211
|
|
213
|
-
|
214
|
-
@scenario.never
|
215
|
-
permissive_argument!
|
216
|
-
reimplementation!
|
217
|
-
end
|
212
|
+
instance_method_name = method_name
|
218
213
|
|
219
|
-
|
220
|
-
|
221
|
-
@
|
222
|
-
|
223
|
-
|
214
|
+
@definition = @space.scenario_definition
|
215
|
+
class_handler = proc do |return_value|
|
216
|
+
double = @space.double(return_value, instance_method_name)
|
217
|
+
@space.scenario(double, @definition)
|
218
|
+
return_value
|
224
219
|
end
|
225
|
-
end
|
226
220
|
|
227
|
-
|
228
|
-
|
221
|
+
builder = ScenarioDefinitionBuilder.new(
|
222
|
+
class_scenario.definition,
|
223
|
+
[],
|
224
|
+
class_handler
|
225
|
+
)
|
226
|
+
builder.stub!
|
227
|
+
builder.probe!
|
229
228
|
end
|
230
|
-
|
231
|
-
def
|
232
|
-
@
|
233
|
-
|
229
|
+
|
230
|
+
def transform!
|
231
|
+
builder = ScenarioDefinitionBuilder.new(@definition, @args, @handler)
|
232
|
+
|
233
|
+
case @strategy
|
234
|
+
when :mock; builder.mock!
|
235
|
+
when :stub; builder.stub!
|
236
|
+
when :do_not_call; builder.do_not_call!
|
237
|
+
else no_strategy_error!
|
238
|
+
end
|
239
|
+
|
240
|
+
if @probe
|
241
|
+
builder.probe!
|
242
|
+
else
|
243
|
+
builder.reimplementation!
|
244
|
+
end
|
234
245
|
end
|
235
246
|
|
236
247
|
def strategy_error!
|
@@ -0,0 +1,277 @@
|
|
1
|
+
module RR
|
2
|
+
# RR::Scenario is the use case for a method call.
|
3
|
+
# It has the ArgumentEqualityExpectation, TimesCalledExpectation,
|
4
|
+
# and the implementation.
|
5
|
+
class ScenarioDefinition
|
6
|
+
ORIGINAL_METHOD = Object.new
|
7
|
+
attr_accessor :times_called,
|
8
|
+
:argument_expectation,
|
9
|
+
:times_matcher,
|
10
|
+
:double,
|
11
|
+
:implementation,
|
12
|
+
:after_call_value,
|
13
|
+
:yields_value,
|
14
|
+
:scenario
|
15
|
+
|
16
|
+
def initialize(space)
|
17
|
+
@space = space
|
18
|
+
@implementation = nil
|
19
|
+
@argument_expectation = nil
|
20
|
+
@times_matcher = nil
|
21
|
+
@after_call_value = nil
|
22
|
+
@yields_value = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
# Scenario#with sets the expectation that the Scenario will receive
|
26
|
+
# the passed in arguments.
|
27
|
+
#
|
28
|
+
# Passing in a block sets the return value.
|
29
|
+
#
|
30
|
+
# mock(subject).method_name.with(1, 2) {:return_value}
|
31
|
+
def with(*args, &returns)
|
32
|
+
@argument_expectation = Expectations::ArgumentEqualityExpectation.new(*args)
|
33
|
+
returns(&returns) if returns
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
# Scenario#with_any_args sets the expectation that the Scenario can receive
|
38
|
+
# any arguments.
|
39
|
+
#
|
40
|
+
# Passing in a block sets the return value.
|
41
|
+
#
|
42
|
+
# mock(subject).method_name.with_any_args {:return_value}
|
43
|
+
def with_any_args(&returns)
|
44
|
+
@argument_expectation = Expectations::AnyArgumentExpectation.new
|
45
|
+
returns(&returns) if returns
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
# Scenario#with_no_args sets the expectation that the Scenario will receive
|
50
|
+
# no arguments.
|
51
|
+
#
|
52
|
+
# Passing in a block sets the return value.
|
53
|
+
#
|
54
|
+
# mock(subject).method_name.with_no_args {:return_value}
|
55
|
+
def with_no_args(&returns)
|
56
|
+
@argument_expectation = Expectations::ArgumentEqualityExpectation.new()
|
57
|
+
returns(&returns) if returns
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
# Scenario#never sets the expectation that the Scenario will never be
|
62
|
+
# called.
|
63
|
+
#
|
64
|
+
# This method does not accept a block because it will never be called.
|
65
|
+
#
|
66
|
+
# mock(subject).method_name.never
|
67
|
+
def never
|
68
|
+
@times_matcher = TimesCalledMatchers::IntegerMatcher.new(0)
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
# Scenario#once sets the expectation that the Scenario will be called
|
73
|
+
# 1 time.
|
74
|
+
#
|
75
|
+
# Passing in a block sets the return value.
|
76
|
+
#
|
77
|
+
# mock(subject).method_name.once {:return_value}
|
78
|
+
def once(&returns)
|
79
|
+
@times_matcher = TimesCalledMatchers::IntegerMatcher.new(1)
|
80
|
+
returns(&returns) if returns
|
81
|
+
self
|
82
|
+
end
|
83
|
+
|
84
|
+
# Scenario#twice sets the expectation that the Scenario will be called
|
85
|
+
# 2 times.
|
86
|
+
#
|
87
|
+
# Passing in a block sets the return value.
|
88
|
+
#
|
89
|
+
# mock(subject).method_name.twice {:return_value}
|
90
|
+
def twice(&returns)
|
91
|
+
@times_matcher = TimesCalledMatchers::IntegerMatcher.new(2)
|
92
|
+
returns(&returns) if returns
|
93
|
+
self
|
94
|
+
end
|
95
|
+
|
96
|
+
# Scenario#at_least sets the expectation that the Scenario
|
97
|
+
# will be called at least n times.
|
98
|
+
# It works by creating a TimesCalledExpectation.
|
99
|
+
#
|
100
|
+
# Passing in a block sets the return value.
|
101
|
+
#
|
102
|
+
# mock(subject).method_name.at_least(4) {:return_value}
|
103
|
+
def at_least(number, &returns)
|
104
|
+
@times_matcher = TimesCalledMatchers::AtLeastMatcher.new(number)
|
105
|
+
returns(&returns) if returns
|
106
|
+
self
|
107
|
+
end
|
108
|
+
|
109
|
+
# Scenario#at_most allows sets the expectation that the Scenario
|
110
|
+
# will be called at most n times.
|
111
|
+
# It works by creating a TimesCalledExpectation.
|
112
|
+
#
|
113
|
+
# Passing in a block sets the return value.
|
114
|
+
#
|
115
|
+
# mock(subject).method_name.at_most(4) {:return_value}
|
116
|
+
def at_most(number, &returns)
|
117
|
+
@times_matcher = TimesCalledMatchers::AtMostMatcher.new(number)
|
118
|
+
returns(&returns) if returns
|
119
|
+
self
|
120
|
+
end
|
121
|
+
|
122
|
+
# Scenario#any_number_of_times sets an that the Scenario will be called
|
123
|
+
# any number of times. This effectively removes the times called expectation
|
124
|
+
# from the Scenarion
|
125
|
+
#
|
126
|
+
# Passing in a block sets the return value.
|
127
|
+
#
|
128
|
+
# mock(subject).method_name.any_number_of_times
|
129
|
+
def any_number_of_times(&returns)
|
130
|
+
@times_matcher = TimesCalledMatchers::AnyTimesMatcher.new
|
131
|
+
returns(&returns) if returns
|
132
|
+
self
|
133
|
+
end
|
134
|
+
|
135
|
+
# Scenario#times creates an TimesCalledExpectation of the passed
|
136
|
+
# in number.
|
137
|
+
#
|
138
|
+
# Passing in a block sets the return value.
|
139
|
+
#
|
140
|
+
# mock(subject).method_name.times(4) {:return_value}
|
141
|
+
def times(matcher_value, &returns)
|
142
|
+
@times_matcher = TimesCalledMatchers::TimesCalledMatcher.create(matcher_value)
|
143
|
+
returns(&returns) if returns
|
144
|
+
self
|
145
|
+
end
|
146
|
+
|
147
|
+
# Scenario#ordered sets the Scenario to have an ordered
|
148
|
+
# expectation.
|
149
|
+
#
|
150
|
+
# Passing in a block sets the return value.
|
151
|
+
#
|
152
|
+
# mock(subject).method_name.ordered {return_value}
|
153
|
+
def ordered(&returns)
|
154
|
+
raise(
|
155
|
+
Errors::ScenarioDefinitionError,
|
156
|
+
"Scenario Definitions must have a dedicated Scenario to be ordered. " <<
|
157
|
+
"For example, using instance_of does not allow ordered to be used. " <<
|
158
|
+
"probe the class's #new method instead."
|
159
|
+
) unless @scenario
|
160
|
+
@ordered = true
|
161
|
+
@space.ordered_scenarios << @scenario unless @space.ordered_scenarios.include?(@scenario)
|
162
|
+
returns(&returns) if returns
|
163
|
+
self
|
164
|
+
end
|
165
|
+
|
166
|
+
# Scenario#ordered? returns true when the Scenario is ordered.
|
167
|
+
#
|
168
|
+
# mock(subject).method_name.ordered?
|
169
|
+
def ordered?
|
170
|
+
@ordered
|
171
|
+
end
|
172
|
+
|
173
|
+
# Scenario#yields sets the Scenario to invoke a passed in block when
|
174
|
+
# the Scenario is called.
|
175
|
+
# An Expection will be raised if no block is passed in when the
|
176
|
+
# Scenario is called.
|
177
|
+
#
|
178
|
+
# Passing in a block sets the return value.
|
179
|
+
#
|
180
|
+
# mock(subject).method_name.yields(yield_arg1, yield_arg2) {return_value}
|
181
|
+
# subject.method_name {|yield_arg1, yield_arg2|}
|
182
|
+
def yields(*args, &returns)
|
183
|
+
@yields_value = args
|
184
|
+
returns(&returns) if returns
|
185
|
+
self
|
186
|
+
end
|
187
|
+
|
188
|
+
# Scenario#after_call creates a callback that occurs after call
|
189
|
+
# is called. The passed in block receives the return value of
|
190
|
+
# the Scenario being called.
|
191
|
+
# An Expection will be raised if no block is passed in.
|
192
|
+
#
|
193
|
+
# mock(subject).method_name {return_value}.after_call {|return_value|}
|
194
|
+
# subject.method_name # return_value
|
195
|
+
#
|
196
|
+
# This feature is built into probes.
|
197
|
+
# probe(User).find('1') {|user| mock(user).valid? {false}}
|
198
|
+
def after_call(&block)
|
199
|
+
raise ArgumentError, "after_call expects a block" unless block
|
200
|
+
@after_call_value = block
|
201
|
+
self
|
202
|
+
end
|
203
|
+
|
204
|
+
# Scenario#returns accepts an argument value or a block.
|
205
|
+
# It will raise an ArgumentError if both are passed in.
|
206
|
+
#
|
207
|
+
# Passing in a block causes Scenario to return the return value of
|
208
|
+
# the passed in block.
|
209
|
+
#
|
210
|
+
# Passing in an argument causes Scenario to return the argument.
|
211
|
+
def returns(value=nil, &implementation)
|
212
|
+
if value && implementation
|
213
|
+
raise ArgumentError, "returns cannot accept both an argument and a block"
|
214
|
+
end
|
215
|
+
if value.nil?
|
216
|
+
implemented_by implementation
|
217
|
+
else
|
218
|
+
implemented_by proc {value}
|
219
|
+
end
|
220
|
+
self
|
221
|
+
end
|
222
|
+
|
223
|
+
# Scenario#implemented_by sets the implementation of the Scenario.
|
224
|
+
# This method takes a Proc or a Method. Passing in a Method allows
|
225
|
+
# the Scenario to accept blocks.
|
226
|
+
#
|
227
|
+
# obj = Object.new
|
228
|
+
# def obj.foobar
|
229
|
+
# yield(1)
|
230
|
+
# end
|
231
|
+
# mock(obj).method_name.implemented_by(obj.method(:foobar))
|
232
|
+
def implemented_by(implementation)
|
233
|
+
@implementation = implementation
|
234
|
+
self
|
235
|
+
end
|
236
|
+
|
237
|
+
# Scenario#implemented_by_original_method sets the implementation
|
238
|
+
# of the Scenario to be the original method.
|
239
|
+
# This is primarily used with probes.
|
240
|
+
#
|
241
|
+
# obj = Object.new
|
242
|
+
# def obj.foobar
|
243
|
+
# yield(1)
|
244
|
+
# end
|
245
|
+
# mock(obj).method_name.implemented_by_original_method
|
246
|
+
# obj.foobar {|arg| puts arg} # puts 1
|
247
|
+
def implemented_by_original_method
|
248
|
+
implemented_by ORIGINAL_METHOD
|
249
|
+
self
|
250
|
+
end
|
251
|
+
|
252
|
+
# Scenario#exact_match? returns true when the passed in arguments
|
253
|
+
# exactly match the ArgumentEqualityExpectation arguments.
|
254
|
+
def exact_match?(*arguments)
|
255
|
+
return false unless @argument_expectation
|
256
|
+
@argument_expectation.exact_match?(*arguments)
|
257
|
+
end
|
258
|
+
|
259
|
+
# Scenario#wildcard_match? returns true when the passed in arguments
|
260
|
+
# wildcard match the ArgumentEqualityExpectation arguments.
|
261
|
+
def wildcard_match?(*arguments)
|
262
|
+
return false unless @argument_expectation
|
263
|
+
@argument_expectation.wildcard_match?(*arguments)
|
264
|
+
end
|
265
|
+
|
266
|
+
def terminal?
|
267
|
+
return false unless @times_matcher
|
268
|
+
@times_matcher.terminal?
|
269
|
+
end
|
270
|
+
|
271
|
+
# The Arguments that this Scenario expects
|
272
|
+
def expected_arguments
|
273
|
+
return [] unless argument_expectation
|
274
|
+
argument_expectation.expected_arguments
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module RR
|
2
|
+
class ScenarioDefinitionBuilder
|
3
|
+
attr_reader :definition
|
4
|
+
|
5
|
+
def initialize(definition, args, handler)
|
6
|
+
@definition = definition
|
7
|
+
@args = args
|
8
|
+
@handler = handler
|
9
|
+
end
|
10
|
+
|
11
|
+
def mock!
|
12
|
+
@definition.with(*@args).once
|
13
|
+
end
|
14
|
+
|
15
|
+
def stub!
|
16
|
+
@definition.any_number_of_times
|
17
|
+
permissive_argument!
|
18
|
+
end
|
19
|
+
|
20
|
+
def do_not_call!
|
21
|
+
@definition.never
|
22
|
+
permissive_argument!
|
23
|
+
reimplementation!
|
24
|
+
end
|
25
|
+
|
26
|
+
def permissive_argument!
|
27
|
+
if @args.empty?
|
28
|
+
@definition.with_any_args
|
29
|
+
else
|
30
|
+
@definition.with(*@args)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def reimplementation!
|
35
|
+
@definition.returns(&@handler)
|
36
|
+
end
|
37
|
+
|
38
|
+
def probe!
|
39
|
+
@definition.implemented_by_original_method
|
40
|
+
@definition.after_call(&@handler) if @handler
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|