remarkable_rails 3.0.1 → 3.0.2

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.
Files changed (31) hide show
  1. data/CHANGELOG +44 -44
  2. data/LICENSE +1 -1
  3. data/README +83 -83
  4. data/lib/remarkable_rails/action_controller/base.rb +24 -24
  5. data/lib/remarkable_rails/action_controller/macro_stubs.rb +493 -493
  6. data/lib/remarkable_rails/action_controller/matchers/assign_to_matcher.rb +72 -72
  7. data/lib/remarkable_rails/action_controller/matchers/filter_params_matcher.rb +21 -21
  8. data/lib/remarkable_rails/action_controller/matchers/redirect_to_matcher.rb +112 -112
  9. data/lib/remarkable_rails/action_controller/matchers/render_template_matcher.rb +140 -140
  10. data/lib/remarkable_rails/action_controller/matchers/respond_with_matcher.rb +118 -118
  11. data/lib/remarkable_rails/action_controller/matchers/route_matcher.rb +51 -51
  12. data/lib/remarkable_rails/action_controller/matchers/set_session_matcher.rb +103 -103
  13. data/lib/remarkable_rails/action_controller/matchers/set_the_flash_matcher.rb +50 -50
  14. data/lib/remarkable_rails/action_view/base.rb +1 -1
  15. data/lib/remarkable_rails/action_view.rb +16 -16
  16. data/lib/remarkable_rails/active_orm.rb +2 -2
  17. data/locale/en.yml +74 -74
  18. data/spec/action_controller/assign_to_matcher_spec.rb +68 -68
  19. data/spec/action_controller/filter_params_matcher_spec.rb +42 -42
  20. data/spec/action_controller/macro_stubs_spec.rb +17 -17
  21. data/spec/action_controller/redirect_to_matcher_spec.rb +60 -60
  22. data/spec/action_controller/render_template_matcher_spec.rb +227 -227
  23. data/spec/action_controller/respond_with_matcher_spec.rb +189 -189
  24. data/spec/action_controller/route_matcher_spec.rb +75 -75
  25. data/spec/action_controller/set_session_matcher_spec.rb +43 -43
  26. data/spec/action_controller/set_the_flash_matcher_spec.rb +1 -1
  27. data/spec/application/application.rb +14 -14
  28. data/spec/application/tasks_controller.rb +34 -34
  29. data/spec/functional_builder.rb +93 -93
  30. data/spec/spec_helper.rb +44 -44
  31. metadata +4 -4
@@ -1,493 +1,493 @@
1
- module Remarkable
2
- module ActionController
3
-
4
- # Macro stubs makes stubs and expectations easier, more readable and DRY.
5
- #
6
- # == Example
7
- #
8
- # Let's jump off to an example:
9
- #
10
- # describe ProjectsController do
11
- # describe :get => :show, :id => 37 do
12
- # expects :find, :on => Project, :with => '37', :returns => proc { mock_project }
13
- #
14
- # should_assign_to :project, :with => proc { mock_project }
15
- # should_render_template 'show'
16
- #
17
- # describe Mime::XML do
18
- # should_assign_to :project
19
- # should_respond_with_content_type Mime::XML
20
- # end
21
- # end
22
- # end
23
- #
24
- # See how the spec is readable: a ProjectsController responding to get show
25
- # expects :find on Project which a mock project and then should assign to
26
- # project and render template 'show'.
27
- #
28
- # Each macro before asserting will check if an action was already performed and
29
- # if not, it runs the expectations and call the action.
30
- #
31
- # In other words, should assign to macro is basically doing:
32
- #
33
- # it 'should assign to project' do
34
- # Project.should_receive(:find).with('37').and_return(mock_project)
35
- # get :show, :id => '37'
36
- # assigns(:project).should == mock_project
37
- # end
38
- #
39
- # On the other hand, should render template is doing something like this:
40
- #
41
- # it 'should render template show' do
42
- # Project.stub!(:find).and_return(mock_project)
43
- # get :show, :id => '37'
44
- # response.should render_template('show')
45
- # end
46
- #
47
- # Now comes the first question: how each macro knows if they should perform
48
- # expectations or stubs?
49
- #
50
- # By default, only should_assign_to macro performs expectations. You can change
51
- # this behavior sending :with_stubs or :with_expectations as options:
52
- #
53
- # should_assign_to :project, :with_stubs => true
54
- # should_render_template 'show', :with_expectations => true
55
- #
56
- # This also works in the rspec way:
57
- #
58
- # it { should assign_to(:project).with_stubs }
59
- # it { should render_tempalte('show').with_expectations }
60
- #
61
- # == mock_models
62
- #
63
- # You don't have to play with proc all the time. You can call mock_models which
64
- # creates a class method that simply returns a proc and a instance method that
65
- # do the actual mock. In other words, it creates:
66
- #
67
- # def self.mock_project
68
- # proc { mock_project }
69
- # end
70
- #
71
- # def mock_project(stubs={})
72
- # @project ||= mock_model(Project, stubs)
73
- # end
74
- #
75
- # Then you can replace those lines:
76
- #
77
- # expects :find, :on => Project, :with => '37', :returns => proc { mock_project }
78
- # should_assign_to :project, :with => proc { mock_project }
79
- #
80
- # For:
81
- #
82
- # expects :find, :on => Project, :with => '37', :returns => mock_project
83
- # should_assign_to :project, :with => mock_project
84
- #
85
- # = Give me more!
86
- #
87
- # If you need to specify the describe description, you can also do:
88
- #
89
- # describe 'my description' do
90
- # get :show, :id => 37
91
- #
92
- # Which is the same as above.
93
- #
94
- # Things start to get even better when we start to talk about nested resources.
95
- # After our ProjectsController is created, we want to create a TasksController:
96
- #
97
- # describe TasksController do
98
- # params :project_id => '42' #=> define params for all requests
99
- #
100
- # # Those two expectations get inherited in all describe groups below
101
- # expects :find_by_title, :on => Project, :with => '42', :returns => mock_project
102
- # expects :tasks, :and_return => Task
103
- #
104
- # describe :get => :show, :id => '37' do
105
- # expects :find, :with => '37', :and_return => mock_task
106
- #
107
- # should_assign_to :project, :task
108
- # should_render_template 'show'
109
- # end
110
- # end
111
- #
112
- # As you noticed, you can define parameters that will be available to all requests,
113
- # using the method <tt>params</tt>.
114
- #
115
- # Finally, if you used expects chain like above, but need to write a spec by
116
- # hand you can invoke the action and expectations with run_expectations!,
117
- # run_stubs! and run_action!. Examples:
118
- #
119
- # describe :get => :new do
120
- # expects :new, :on => Project, :returns => mock_project
121
- #
122
- # it "should do something different" do
123
- # run_action!
124
- # # do you assertions here
125
- # end
126
- # end
127
- #
128
- # = Performance!
129
- #
130
- # Remarkable comes with a new way to speed up your tests. It perform the action
131
- # inside a before(:all), so you can do:
132
- #
133
- # describe "responding to GET show" do
134
- # get! :show, :id => 37
135
- #
136
- # should_assign_to :task
137
- # should_render_template :show
138
- # end
139
- #
140
- # Or in the compact way:
141
- #
142
- # describe :get! => :show, :id => 37
143
- #
144
- # The action will be performed just once before asserting the assignment and
145
- # the template. If any error happens while performing the action rspec will
146
- # output an error but ALL the examples inside the example group (describe) won't
147
- # be run.
148
- #
149
- # By now, the bang methods works only when integrate_views are true and this is
150
- # when you must have the bigger performance gain.
151
- #
152
- # This comes with some rspec and rspec rails tweakings. So if you want to do
153
- # something before the action is performed (stubs something or log someone in
154
- # session), you have to do it giving a block to the action method:
155
- #
156
- # get! :show, :id => 37 do
157
- # login_as(mock_user)
158
- # end
159
- #
160
- # You can still use the compact way and give the block:
161
- #
162
- # describe :get => :show, :id => 37 do
163
- # get! do
164
- # login_as(mock_user)
165
- # end
166
- # end
167
- #
168
- module MacroStubs
169
- HTTP_VERBS_METHODS = [:get, :get!, :post, :post!, :put, :put!, :delete, :delete!]
170
-
171
- def self.included(base) #:nodoc:
172
- base.extend ClassMethods
173
- base.class_inheritable_reader :expects_chain, :default_action, :default_mime,
174
- :default_verb, :default_params, :before_all_block
175
- end
176
-
177
- module ClassMethods
178
-
179
- # Creates a chain that will be evaluated as stub or expectation. The
180
- # first parameter is the method expected.
181
- #
182
- # == Options
183
- #
184
- # * <tt>:on</tt> - Tell which object will receive the expected method.
185
- # This option is always required.
186
- #
187
- # * <tt>:with</tt> - Tell each parameters will be sent with the expected
188
- # method. This option is used only in expectations and is optional.
189
- #
190
- # * <tt>:returns</tt> - Tell what the expectations should return. Not
191
- # required.
192
- #
193
- # * <tt>:times</tt> - The number of times the object will receive the
194
- # method. Used only in expectations and when not given, defaults to 1.
195
- #
196
- # == Example
197
- #
198
- # expects :new, :on => Project, :returns => :mock_project, :times => 2
199
- #
200
- def expects(*args)
201
- write_inheritable_array(:expects_chain, [args])
202
- end
203
-
204
- # The mime type of the request. The value given will be called transformed
205
- # into a string and set in the @request.env['HTTP_ACCEPT'] variable.
206
- #
207
- # == Examples
208
- #
209
- # mime Mime::XML
210
- # mime 'application/xml+rss'
211
- #
212
- def mime(mime)
213
- write_inheritable_attribute(:default_mime, mime.to_s)
214
- end
215
-
216
- # The params used for the request. Calls are always nested:
217
- #
218
- # == Examples
219
- #
220
- # describe TasksController do
221
- # params :project_id => 42
222
- #
223
- # describe :get => :show, :id => 37 do
224
- # # will request with params {:id => 37, :project_id => 42}
225
- # end
226
- # end
227
- #
228
- def params(params)
229
- write_inheritable_hash(:default_params, params)
230
- end
231
-
232
- [:get, :post, :put, :delete].each do |verb|
233
- module_eval <<-VERB, __FILE__, __LINE__
234
- # Declares that we want to do a #{verb} request in the given action
235
- # and with the given params.
236
- #
237
- # == Examples
238
- #
239
- # #{verb} :action, :id => 42
240
- #
241
- def #{verb}(action, params={})
242
- params(params)
243
- write_inheritable_attribute(:default_verb, #{verb.inspect})
244
- write_inheritable_attribute(:default_action, action)
245
- end
246
- VERB
247
- end
248
-
249
- [:get!, :post!, :put!, :delete!].each do |verb|
250
- module_eval <<-VERB, __FILE__, __LINE__
251
- # Declares that we want to do a #{verb} request in the given action
252
- # and with the given params, but the action is performed just once
253
- # in the describe group. In other words, it's performed in a
254
- # before(:all) filter.
255
- #
256
- # == Examples
257
- #
258
- # #{verb} :action, :id => 42
259
- #
260
- def #{verb}(action=nil, params={}, &block)
261
- #{verb.to_s.chop}(action, params) if action
262
- write_inheritable_array(:before_all_block, [block]) if block
263
- run_callbacks_once!
264
- end
265
- VERB
266
- end
267
-
268
- # Undefine the method run_callbacks so rspec won't run them in the
269
- # before and after :each cycle. Then we redefine it as run_callbacks_once,
270
- # which will be used as an before(:all) and after(:all) filter.
271
- #
272
- def run_callbacks_once!(&block) #:nodoc:
273
- unless instance_methods.any?{|m| m.to_s == 'run_callbacks_once' }
274
- alias_method :run_callbacks_once, :run_callbacks
275
- class_eval "def run_callbacks(*args); end"
276
-
277
- before(:all) do
278
- setup_mocks_for_rspec
279
- run_callbacks_once :setup
280
-
281
- before_all_block.each do |block|
282
- instance_eval(&block)
283
- end if before_all_block
284
-
285
- run_action!
286
- verify_mocks_for_rspec
287
- teardown_mocks_for_rspec
288
- end
289
-
290
- after(:all) do
291
- run_callbacks_once :teardown
292
- end
293
- end
294
- end
295
-
296
- # Overwrites describe to provide quick action description with I18n.
297
- #
298
- # You can now do:
299
- #
300
- # describe :get => :show, :id => 37
301
- #
302
- # Which is the same as:
303
- #
304
- # describe 'responding to #GET show' do
305
- # get :show, :id => 37
306
- #
307
- # And do this:
308
- #
309
- # describe Mime::XML
310
- #
311
- # Which is the same as:
312
- #
313
- # describe 'with xml' do
314
- # mime Mime::XML
315
- #
316
- # The string can be localized using I18n. An example yml file is:
317
- #
318
- # locale:
319
- # remarkable:
320
- # action_controller:
321
- # responding: "responding to #{{verb}} {{action}}"
322
- # mime_type: "with {{format}} ({{content_type}})"
323
- #
324
- # And load the locale file with:
325
- #
326
- # Remarkable.add_locale locale_path
327
- #
328
- def describe(*args, &block)
329
- options = args.first.is_a?(Hash) ? args.first : {}
330
- verb = (options.keys & HTTP_VERBS_METHODS).first
331
-
332
- if verb
333
- action = options.delete(verb)
334
- verb = verb.to_s
335
-
336
- description = Remarkable.t 'remarkable.action_controller.responding',
337
- :default => "responding to ##{verb.upcase} #{action}",
338
- :verb => verb.sub('!', '').upcase, :action => action
339
-
340
- send_args = [ verb, action, options ]
341
- elsif args.first.is_a?(Mime::Type)
342
- mime = args.first
343
-
344
- description = Remarkable.t 'remarkable.action_controller.mime_type',
345
- :default => "with #{mime.to_sym}",
346
- :format => mime.to_sym, :content_type => mime.to_s
347
-
348
- send_args = [ :mime, mime ]
349
- else # return if no special type was found
350
- return super(*args, &block)
351
- end
352
-
353
- args.shift
354
- args.unshift(description)
355
-
356
- # Creates an example group, send the method and eval the given block.
357
- #
358
- example_group = super(*args) do
359
- send(*send_args)
360
- instance_eval(&block)
361
- end
362
- end
363
-
364
- # Creates mock methods automatically.
365
- #
366
- # == Options
367
- #
368
- # * <tt>:class_method</tt> - When set to false, does not create the
369
- # class method which returns a proc.
370
- #
371
- # == Examples
372
- #
373
- # Doing this:
374
- #
375
- # describe ProjectsController do
376
- # mock_models :project
377
- # end
378
- #
379
- # Will create a class and instance mock method for you:
380
- #
381
- # def self.mock_project
382
- # proc { mock_project }
383
- # end
384
- #
385
- # def mock_project(stubs={})
386
- # @project ||= mock_model(Project, stubs)
387
- # end
388
- #
389
- # If you want to create just the instance method, you can give
390
- # :class_method => false as option.
391
- #
392
- def mock_models(*models)
393
- options = models.extract_options!
394
- options = { :class_method => true }.merge(options)
395
-
396
- models.each do |model|
397
- self.class_eval <<-METHOD
398
- #{"def self.mock_#{model}; proc { mock_#{model} }; end" if options[:class_method]}
399
-
400
- def mock_#{model}(stubs={})
401
- @#{model} ||= mock_model(#{model.to_s.classify}, stubs)
402
- end
403
- METHOD
404
- end
405
- end
406
-
407
- end
408
-
409
- protected
410
-
411
- # Evaluates the expectation chain as stub or expectations.
412
- #
413
- def evaluate_expectation_chain(use_expectations=true) #:nodoc:
414
- return if self.expects_chain.nil?
415
-
416
- self.expects_chain.each do |method, default_options|
417
- options = default_options.dup
418
-
419
- # Those are used both in expectations and stubs
420
- object = evaluate_value(options.delete(:on))
421
- return_value = evaluate_value(options.delete(:returns))
422
-
423
- raise ScriptError, "You have to give me :on as an option when calling :expects." if object.nil?
424
-
425
- if use_expectations
426
- with = evaluate_value(options.delete(:with))
427
- times = options.delete(:times) || 1
428
-
429
- chain = object.should_receive(method)
430
- chain = chain.with(with) if with
431
- chain = chain.exactly(times).times
432
- else
433
- chain = object.stub!(method)
434
- end
435
- chain = chain.and_return(return_value)
436
- end
437
- end
438
-
439
- # Instance method run_stubs! if someone wants to declare additional
440
- # tests and call the stubs inside of it.
441
- #
442
- def run_stubs!
443
- evaluate_expectation_chain(false)
444
- end
445
-
446
- # Instance method run_expectations! if someone wants to declare
447
- # additional tests and call the stubs inside of it.
448
- #
449
- def run_expectations!
450
- evaluate_expectation_chain(true)
451
- end
452
-
453
- # Run the action declared in the describe group, but before runs also
454
- # the expectations. If an action was already performed, it doesn't run
455
- # anything at all and returns false.
456
- #
457
- # The first parameter is if you want to run expectations or stubs. You
458
- # can also supply the verb (get, post, put or delete), which action to
459
- # call, parameters and the mime type. If any of those parameters are
460
- # supplied, they override the current definition.
461
- #
462
- def run_action!(use_expectations=true, verb=nil, action=nil, params=nil, mime=nil)
463
- return false if controller.send(:performed?)
464
-
465
- evaluate_expectation_chain(use_expectations)
466
-
467
- mime ||= default_mime
468
- verb ||= default_verb
469
- action ||= default_action
470
- params ||= default_params
471
-
472
- raise ScriptError, "No action was performed or declared." unless verb && action
473
-
474
- request.env["HTTP_ACCEPT"] ||= mime.to_s if mime
475
- send(verb, action, params)
476
- end
477
-
478
- # Evaluate a given value.
479
- #
480
- # This allows procs to be given to the expectation chain and they will
481
- # be evaluated in the instance binding.
482
- #
483
- def evaluate_value(duck) #:nodoc:
484
- if duck.is_a?(Proc)
485
- self.instance_eval(&duck)
486
- else
487
- duck
488
- end
489
- end
490
-
491
- end
492
- end
493
- end
1
+ module Remarkable
2
+ module ActionController
3
+
4
+ # Macro stubs makes stubs and expectations easier, more readable and DRY.
5
+ #
6
+ # == Example
7
+ #
8
+ # Let's jump off to an example:
9
+ #
10
+ # describe ProjectsController do
11
+ # describe :get => :show, :id => 37 do
12
+ # expects :find, :on => Project, :with => '37', :returns => proc { mock_project }
13
+ #
14
+ # should_assign_to :project, :with => proc { mock_project }
15
+ # should_render_template 'show'
16
+ #
17
+ # describe Mime::XML do
18
+ # should_assign_to :project
19
+ # should_respond_with_content_type Mime::XML
20
+ # end
21
+ # end
22
+ # end
23
+ #
24
+ # See how the spec is readable: a ProjectsController responding to get show
25
+ # expects :find on Project which a mock project and then should assign to
26
+ # project and render template 'show'.
27
+ #
28
+ # Each macro before asserting will check if an action was already performed and
29
+ # if not, it runs the expectations and call the action.
30
+ #
31
+ # In other words, should assign to macro is basically doing:
32
+ #
33
+ # it 'should assign to project' do
34
+ # Project.should_receive(:find).with('37').and_return(mock_project)
35
+ # get :show, :id => '37'
36
+ # assigns(:project).should == mock_project
37
+ # end
38
+ #
39
+ # On the other hand, should render template is doing something like this:
40
+ #
41
+ # it 'should render template show' do
42
+ # Project.stub!(:find).and_return(mock_project)
43
+ # get :show, :id => '37'
44
+ # response.should render_template('show')
45
+ # end
46
+ #
47
+ # Now comes the first question: how each macro knows if they should perform
48
+ # expectations or stubs?
49
+ #
50
+ # By default, only should_assign_to macro performs expectations. You can change
51
+ # this behavior sending :with_stubs or :with_expectations as options:
52
+ #
53
+ # should_assign_to :project, :with_stubs => true
54
+ # should_render_template 'show', :with_expectations => true
55
+ #
56
+ # This also works in the rspec way:
57
+ #
58
+ # it { should assign_to(:project).with_stubs }
59
+ # it { should render_tempalte('show').with_expectations }
60
+ #
61
+ # == mock_models
62
+ #
63
+ # You don't have to play with proc all the time. You can call mock_models which
64
+ # creates a class method that simply returns a proc and a instance method that
65
+ # do the actual mock. In other words, it creates:
66
+ #
67
+ # def self.mock_project
68
+ # proc { mock_project }
69
+ # end
70
+ #
71
+ # def mock_project(stubs={})
72
+ # @project ||= mock_model(Project, stubs)
73
+ # end
74
+ #
75
+ # Then you can replace those lines:
76
+ #
77
+ # expects :find, :on => Project, :with => '37', :returns => proc { mock_project }
78
+ # should_assign_to :project, :with => proc { mock_project }
79
+ #
80
+ # For:
81
+ #
82
+ # expects :find, :on => Project, :with => '37', :returns => mock_project
83
+ # should_assign_to :project, :with => mock_project
84
+ #
85
+ # = Give me more!
86
+ #
87
+ # If you need to specify the describe description, you can also do:
88
+ #
89
+ # describe 'my description' do
90
+ # get :show, :id => 37
91
+ #
92
+ # Which is the same as above.
93
+ #
94
+ # Things start to get even better when we start to talk about nested resources.
95
+ # After our ProjectsController is created, we want to create a TasksController:
96
+ #
97
+ # describe TasksController do
98
+ # params :project_id => '42' #=> define params for all requests
99
+ #
100
+ # # Those two expectations get inherited in all describe groups below
101
+ # expects :find_by_title, :on => Project, :with => '42', :returns => mock_project
102
+ # expects :tasks, :and_return => Task
103
+ #
104
+ # describe :get => :show, :id => '37' do
105
+ # expects :find, :with => '37', :and_return => mock_task
106
+ #
107
+ # should_assign_to :project, :task
108
+ # should_render_template 'show'
109
+ # end
110
+ # end
111
+ #
112
+ # As you noticed, you can define parameters that will be available to all requests,
113
+ # using the method <tt>params</tt>.
114
+ #
115
+ # Finally, if you used expects chain like above, but need to write a spec by
116
+ # hand you can invoke the action and expectations with run_expectations!,
117
+ # run_stubs! and run_action!. Examples:
118
+ #
119
+ # describe :get => :new do
120
+ # expects :new, :on => Project, :returns => mock_project
121
+ #
122
+ # it "should do something different" do
123
+ # run_action!
124
+ # # do you assertions here
125
+ # end
126
+ # end
127
+ #
128
+ # = Performance!
129
+ #
130
+ # Remarkable comes with a new way to speed up your tests. It perform the action
131
+ # inside a before(:all), so you can do:
132
+ #
133
+ # describe "responding to GET show" do
134
+ # get! :show, :id => 37
135
+ #
136
+ # should_assign_to :task
137
+ # should_render_template :show
138
+ # end
139
+ #
140
+ # Or in the compact way:
141
+ #
142
+ # describe :get! => :show, :id => 37
143
+ #
144
+ # The action will be performed just once before asserting the assignment and
145
+ # the template. If any error happens while performing the action rspec will
146
+ # output an error but ALL the examples inside the example group (describe) won't
147
+ # be run.
148
+ #
149
+ # By now, the bang methods works only when integrate_views are true and this is
150
+ # when you must have the bigger performance gain.
151
+ #
152
+ # This comes with some rspec and rspec rails tweakings. So if you want to do
153
+ # something before the action is performed (stubs something or log someone in
154
+ # session), you have to do it giving a block to the action method:
155
+ #
156
+ # get! :show, :id => 37 do
157
+ # login_as(mock_user)
158
+ # end
159
+ #
160
+ # You can still use the compact way and give the block:
161
+ #
162
+ # describe :get => :show, :id => 37 do
163
+ # get! do
164
+ # login_as(mock_user)
165
+ # end
166
+ # end
167
+ #
168
+ module MacroStubs
169
+ HTTP_VERBS_METHODS = [:get, :get!, :post, :post!, :put, :put!, :delete, :delete!]
170
+
171
+ def self.included(base) #:nodoc:
172
+ base.extend ClassMethods
173
+ base.class_inheritable_reader :expects_chain, :default_action, :default_mime,
174
+ :default_verb, :default_params, :before_all_block
175
+ end
176
+
177
+ module ClassMethods
178
+
179
+ # Creates a chain that will be evaluated as stub or expectation. The
180
+ # first parameter is the method expected.
181
+ #
182
+ # == Options
183
+ #
184
+ # * <tt>:on</tt> - Tell which object will receive the expected method.
185
+ # This option is always required.
186
+ #
187
+ # * <tt>:with</tt> - Tell each parameters will be sent with the expected
188
+ # method. This option is used only in expectations and is optional.
189
+ #
190
+ # * <tt>:returns</tt> - Tell what the expectations should return. Not
191
+ # required.
192
+ #
193
+ # * <tt>:times</tt> - The number of times the object will receive the
194
+ # method. Used only in expectations and when not given, defaults to 1.
195
+ #
196
+ # == Example
197
+ #
198
+ # expects :new, :on => Project, :returns => :mock_project, :times => 2
199
+ #
200
+ def expects(*args)
201
+ write_inheritable_array(:expects_chain, [args])
202
+ end
203
+
204
+ # The mime type of the request. The value given will be called transformed
205
+ # into a string and set in the @request.env['HTTP_ACCEPT'] variable.
206
+ #
207
+ # == Examples
208
+ #
209
+ # mime Mime::XML
210
+ # mime 'application/xml+rss'
211
+ #
212
+ def mime(mime)
213
+ write_inheritable_attribute(:default_mime, mime.to_s)
214
+ end
215
+
216
+ # The params used for the request. Calls are always nested:
217
+ #
218
+ # == Examples
219
+ #
220
+ # describe TasksController do
221
+ # params :project_id => 42
222
+ #
223
+ # describe :get => :show, :id => 37 do
224
+ # # will request with params {:id => 37, :project_id => 42}
225
+ # end
226
+ # end
227
+ #
228
+ def params(params)
229
+ write_inheritable_hash(:default_params, params)
230
+ end
231
+
232
+ [:get, :post, :put, :delete].each do |verb|
233
+ module_eval <<-VERB, __FILE__, __LINE__
234
+ # Declares that we want to do a #{verb} request in the given action
235
+ # and with the given params.
236
+ #
237
+ # == Examples
238
+ #
239
+ # #{verb} :action, :id => 42
240
+ #
241
+ def #{verb}(action, params={})
242
+ params(params)
243
+ write_inheritable_attribute(:default_verb, #{verb.inspect})
244
+ write_inheritable_attribute(:default_action, action)
245
+ end
246
+ VERB
247
+ end
248
+
249
+ [:get!, :post!, :put!, :delete!].each do |verb|
250
+ module_eval <<-VERB, __FILE__, __LINE__
251
+ # Declares that we want to do a #{verb} request in the given action
252
+ # and with the given params, but the action is performed just once
253
+ # in the describe group. In other words, it's performed in a
254
+ # before(:all) filter.
255
+ #
256
+ # == Examples
257
+ #
258
+ # #{verb} :action, :id => 42
259
+ #
260
+ def #{verb}(action=nil, params={}, &block)
261
+ #{verb.to_s.chop}(action, params) if action
262
+ write_inheritable_array(:before_all_block, [block]) if block
263
+ run_callbacks_once!
264
+ end
265
+ VERB
266
+ end
267
+
268
+ # Undefine the method run_callbacks so rspec won't run them in the
269
+ # before and after :each cycle. Then we redefine it as run_callbacks_once,
270
+ # which will be used as an before(:all) and after(:all) filter.
271
+ #
272
+ def run_callbacks_once!(&block) #:nodoc:
273
+ unless instance_methods.any?{|m| m.to_s == 'run_callbacks_once' }
274
+ alias_method :run_callbacks_once, :run_callbacks
275
+ class_eval "def run_callbacks(*args); end"
276
+
277
+ before(:all) do
278
+ setup_mocks_for_rspec
279
+ run_callbacks_once :setup
280
+
281
+ before_all_block.each do |block|
282
+ instance_eval(&block)
283
+ end if before_all_block
284
+
285
+ run_action!
286
+ verify_mocks_for_rspec
287
+ teardown_mocks_for_rspec
288
+ end
289
+
290
+ after(:all) do
291
+ run_callbacks_once :teardown
292
+ end
293
+ end
294
+ end
295
+
296
+ # Overwrites describe to provide quick action description with I18n.
297
+ #
298
+ # You can now do:
299
+ #
300
+ # describe :get => :show, :id => 37
301
+ #
302
+ # Which is the same as:
303
+ #
304
+ # describe 'responding to #GET show' do
305
+ # get :show, :id => 37
306
+ #
307
+ # And do this:
308
+ #
309
+ # describe Mime::XML
310
+ #
311
+ # Which is the same as:
312
+ #
313
+ # describe 'with xml' do
314
+ # mime Mime::XML
315
+ #
316
+ # The string can be localized using I18n. An example yml file is:
317
+ #
318
+ # locale:
319
+ # remarkable:
320
+ # action_controller:
321
+ # responding: "responding to #{{verb}} {{action}}"
322
+ # mime_type: "with {{format}} ({{content_type}})"
323
+ #
324
+ # And load the locale file with:
325
+ #
326
+ # Remarkable.add_locale locale_path
327
+ #
328
+ def describe(*args, &block)
329
+ options = args.first.is_a?(Hash) ? args.first : {}
330
+ verb = (options.keys & HTTP_VERBS_METHODS).first
331
+
332
+ if verb
333
+ action = options.delete(verb)
334
+ verb = verb.to_s
335
+
336
+ description = Remarkable.t 'remarkable.action_controller.responding',
337
+ :default => "responding to ##{verb.upcase} #{action}",
338
+ :verb => verb.sub('!', '').upcase, :action => action
339
+
340
+ send_args = [ verb, action, options ]
341
+ elsif args.first.is_a?(Mime::Type)
342
+ mime = args.first
343
+
344
+ description = Remarkable.t 'remarkable.action_controller.mime_type',
345
+ :default => "with #{mime.to_sym}",
346
+ :format => mime.to_sym, :content_type => mime.to_s
347
+
348
+ send_args = [ :mime, mime ]
349
+ else # return if no special type was found
350
+ return super(*args, &block)
351
+ end
352
+
353
+ args.shift
354
+ args.unshift(description)
355
+
356
+ # Creates an example group, send the method and eval the given block.
357
+ #
358
+ example_group = super(*args) do
359
+ send(*send_args)
360
+ instance_eval(&block)
361
+ end
362
+ end
363
+
364
+ # Creates mock methods automatically.
365
+ #
366
+ # == Options
367
+ #
368
+ # * <tt>:class_method</tt> - When set to false, does not create the
369
+ # class method which returns a proc.
370
+ #
371
+ # == Examples
372
+ #
373
+ # Doing this:
374
+ #
375
+ # describe ProjectsController do
376
+ # mock_models :project
377
+ # end
378
+ #
379
+ # Will create a class and instance mock method for you:
380
+ #
381
+ # def self.mock_project
382
+ # proc { mock_project }
383
+ # end
384
+ #
385
+ # def mock_project(stubs={})
386
+ # @project ||= mock_model(Project, stubs)
387
+ # end
388
+ #
389
+ # If you want to create just the instance method, you can give
390
+ # :class_method => false as option.
391
+ #
392
+ def mock_models(*models)
393
+ options = models.extract_options!
394
+ options = { :class_method => true }.merge(options)
395
+
396
+ models.each do |model|
397
+ self.class_eval <<-METHOD
398
+ #{"def self.mock_#{model}; proc { mock_#{model} }; end" if options[:class_method]}
399
+
400
+ def mock_#{model}(stubs={})
401
+ @#{model} ||= mock_model(#{model.to_s.classify}, stubs)
402
+ end
403
+ METHOD
404
+ end
405
+ end
406
+
407
+ end
408
+
409
+ protected
410
+
411
+ # Evaluates the expectation chain as stub or expectations.
412
+ #
413
+ def evaluate_expectation_chain(use_expectations=true) #:nodoc:
414
+ return if self.expects_chain.nil?
415
+
416
+ self.expects_chain.each do |method, default_options|
417
+ options = default_options.dup
418
+
419
+ # Those are used both in expectations and stubs
420
+ object = evaluate_value(options.delete(:on))
421
+ return_value = evaluate_value(options.delete(:returns))
422
+
423
+ raise ScriptError, "You have to give me :on as an option when calling :expects." if object.nil?
424
+
425
+ if use_expectations
426
+ with = evaluate_value(options.delete(:with))
427
+ times = options.delete(:times) || 1
428
+
429
+ chain = object.should_receive(method)
430
+ chain = chain.with(with) if with
431
+ chain = chain.exactly(times).times
432
+ else
433
+ chain = object.stub!(method)
434
+ end
435
+ chain = chain.and_return(return_value)
436
+ end
437
+ end
438
+
439
+ # Instance method run_stubs! if someone wants to declare additional
440
+ # tests and call the stubs inside of it.
441
+ #
442
+ def run_stubs!
443
+ evaluate_expectation_chain(false)
444
+ end
445
+
446
+ # Instance method run_expectations! if someone wants to declare
447
+ # additional tests and call the stubs inside of it.
448
+ #
449
+ def run_expectations!
450
+ evaluate_expectation_chain(true)
451
+ end
452
+
453
+ # Run the action declared in the describe group, but before runs also
454
+ # the expectations. If an action was already performed, it doesn't run
455
+ # anything at all and returns false.
456
+ #
457
+ # The first parameter is if you want to run expectations or stubs. You
458
+ # can also supply the verb (get, post, put or delete), which action to
459
+ # call, parameters and the mime type. If any of those parameters are
460
+ # supplied, they override the current definition.
461
+ #
462
+ def run_action!(use_expectations=true, verb=nil, action=nil, params=nil, mime=nil)
463
+ return false if controller.send(:performed?)
464
+
465
+ evaluate_expectation_chain(use_expectations)
466
+
467
+ mime ||= default_mime
468
+ verb ||= default_verb
469
+ action ||= default_action
470
+ params ||= default_params
471
+
472
+ raise ScriptError, "No action was performed or declared." unless verb && action
473
+
474
+ request.env["HTTP_ACCEPT"] ||= mime.to_s if mime
475
+ send(verb, action, params)
476
+ end
477
+
478
+ # Evaluate a given value.
479
+ #
480
+ # This allows procs to be given to the expectation chain and they will
481
+ # be evaluated in the instance binding.
482
+ #
483
+ def evaluate_value(duck) #:nodoc:
484
+ if duck.is_a?(Proc)
485
+ self.instance_eval(&duck)
486
+ else
487
+ duck
488
+ end
489
+ end
490
+
491
+ end
492
+ end
493
+ end