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.
@@ -9,7 +9,7 @@ module RR
9
9
  class ScenarioCreator
10
10
  NO_SUBJECT_ARG = Object.new
11
11
 
12
- attr_reader :space, :subject
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
- protected
189
- def transform!
190
- case @strategy
191
- when :mock; mock!
192
- when :stub; stub!
193
- when :do_not_call; do_not_call!
194
- else no_strategy_error!
195
- end
196
-
197
- if @probe
198
- probe!
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
- reimplementation!
195
+ setup_scenario(subject, method_name)
201
196
  end
197
+ transform!
198
+ @definition
202
199
  end
203
-
204
- def mock!
205
- @scenario.with(*@args).once
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 stub!
209
- @scenario.any_number_of_times
210
- permissive_argument!
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
- def do_not_call!
214
- @scenario.never
215
- permissive_argument!
216
- reimplementation!
217
- end
212
+ instance_method_name = method_name
218
213
 
219
- def permissive_argument!
220
- if @args.empty?
221
- @scenario.with_any_args
222
- else
223
- @scenario.with(*@args)
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
- def reimplementation!
228
- @scenario.returns(&@handler)
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 probe!
232
- @scenario.implemented_by_original_method
233
- @scenario.after_call(&@handler) if @handler
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