rr 0.3.3 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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