Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 459 lines (404 sloc) 16.83 kb
542ddde Joshua Peek Move helpers specific to functional tests out of TestProcess into AC::Te...
josh authored
1 require 'rack/session/abstract/id'
76f024a Xavier Noria adds missing requires for Object#blank? and Object#present?
fxn authored
2 require 'active_support/core_ext/object/blank'
2cc0cac Michael Koziarski Introduce TestCase subclasses for testing rails applications allowing te...
NZKoz authored
3
4 module ActionController
947f86c Modify assert_template to use instrumentation
Carlhuda authored
5 module TemplateAssertions
6 extend ActiveSupport::Concern
7
8 included do
9 setup :setup_subscriptions
10 teardown :teardown_subscriptions
11 end
12
13 def setup_subscriptions
14 @partials = Hash.new(0)
15 @templates = Hash.new(0)
d9375f3 Modify assert_template to use notifications. Also, remove ActionControll...
Carlhuda authored
16 @layouts = Hash.new(0)
17
18 ActiveSupport::Notifications.subscribe("action_view.render_template") do |name, start, finish, id, payload|
19 path = payload[:layout]
20 @layouts[path] += 1
21 end
22
a6dc227 José Valim Mark bang instrumentations as something that you shuold not be listening...
josevalim authored
23 ActiveSupport::Notifications.subscribe("action_view.render_template!") do |name, start, finish, id, payload|
947f86c Modify assert_template to use instrumentation
Carlhuda authored
24 path = payload[:virtual_path]
25 next unless path
26 partial = path =~ /^.*\/_[^\/]*$/
27 if partial
28 @partials[path] += 1
29 @partials[path.split("/").last] += 1
30 @templates[path] += 1
31 else
32 @templates[path] += 1
33 end
34 end
35 end
36
37 def teardown_subscriptions
a6dc227 José Valim Mark bang instrumentations as something that you shuold not be listening...
josevalim authored
38 ActiveSupport::Notifications.unsubscribe("action_view.render_template!")
947f86c Modify assert_template to use instrumentation
Carlhuda authored
39 end
40
41 # Asserts that the request was rendered with the appropriate template file or partials
42 #
43 # ==== Examples
44 #
45 # # assert that the "new" view template was rendered
46 # assert_template "new"
47 #
48 # # assert that the "_customer" partial was rendered twice
49 # assert_template :partial => '_customer', :count => 2
50 #
51 # # assert that no partials were rendered
52 # assert_template :partial => false
53 #
54 def assert_template(options = {}, message = nil)
55 validate_request!
56
57 case options
58 when NilClass, String
59 rendered = @templates
60 msg = build_message(message,
61 "expecting <?> but rendering with <?>",
62 options, rendered.keys.join(', '))
63 assert_block(msg) do
64 if options.nil?
65 @templates.blank?
66 else
67 rendered.any? { |t,num| t.match(options) }
68 end
69 end
70 when Hash
71 if expected_partial = options[:partial]
72 if expected_count = options[:count]
73 actual_count = @partials[expected_partial]
74 # actual_count = found.nil? ? 0 : found[1]
75 msg = build_message(message,
76 "expecting ? to be rendered ? time(s) but rendered ? time(s)",
77 expected_partial, expected_count, actual_count)
78 assert(actual_count == expected_count.to_i, msg)
d9375f3 Modify assert_template to use notifications. Also, remove ActionControll...
Carlhuda authored
79 elsif options.key?(:layout)
80 msg = build_message(message,
81 "expecting layout <?> but action rendered <?>",
82 expected_layout, @layouts.keys)
83
84 case layout = options[:layout]
85 when String
86 assert(@layouts.include?(expected_layout), msg)
87 when Regexp
88 assert(@layouts.any? {|l| l =~ layout }, msg)
89 when nil
90 assert(@layouts.empty?, msg)
91 end
947f86c Modify assert_template to use instrumentation
Carlhuda authored
92 else
93 msg = build_message(message,
94 "expecting partial <?> but action rendered <?>",
95 options[:partial], @partials.keys)
96 assert(@partials.include?(expected_partial), msg)
97 end
98 else
99 assert @partials.empty?,
100 "Expected no partials to be rendered"
101 end
102 end
103 end
104 end
105
542ddde Joshua Peek Move helpers specific to functional tests out of TestProcess into AC::Te...
josh authored
106 class TestRequest < ActionDispatch::TestRequest #:nodoc:
107 def initialize(env = {})
108 super
109
110 self.session = TestSession.new
111 self.session_options = TestSession::DEFAULT_OPTIONS.merge(:id => ActiveSupport::SecureRandom.hex(16))
112 end
113
a1df259 Joshua Peek Replace decaying routing internals w/ rack-mount
josh authored
114 class Result < ::Array #:nodoc:
115 def to_s() join '/' end
116 def self.new_escaped(strings)
117 new strings.collect {|str| URI.unescape str}
118 end
119 end
120
cdf8c35 Joshua Peek Consistent routing language
josh authored
121 def assign_parameters(routes, controller_path, action, parameters = {})
542ddde Joshua Peek Move helpers specific to functional tests out of TestProcess into AC::Te...
josh authored
122 parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action)
cdf8c35 Joshua Peek Consistent routing language
josh authored
123 extra_keys = routes.extra_keys(parameters)
542ddde Joshua Peek Move helpers specific to functional tests out of TestProcess into AC::Te...
josh authored
124 non_path_parameters = get? ? query_parameters : request_parameters
125 parameters.each do |key, value|
126 if value.is_a? Fixnum
127 value = value.to_s
128 elsif value.is_a? Array
a1df259 Joshua Peek Replace decaying routing internals w/ rack-mount
josh authored
129 value = Result.new(value)
542ddde Joshua Peek Move helpers specific to functional tests out of TestProcess into AC::Te...
josh authored
130 end
131
132 if extra_keys.include?(key.to_sym)
133 non_path_parameters[key] = value
134 else
135 path_parameters[key.to_s] = value
136 end
137 end
138
139 params = self.request_parameters.dup
140
141 %w(controller action only_path).each do |k|
142 params.delete(k)
143 params.delete(k.to_sym)
144 end
145
146 data = params.to_query
147 @env['CONTENT_LENGTH'] = data.length.to_s
148 @env['rack.input'] = StringIO.new(data)
149 end
150
151 def recycle!
152 @formats = nil
153 @env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ }
154 @env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
155 @env['action_dispatch.request.query_parameters'] = {}
156 end
157 end
158
159 class TestResponse < ActionDispatch::TestResponse
160 def recycle!
161 @status = 200
162 @header = {}
163 @writer = lambda { |x| @body << x }
164 @block = nil
165 @length = 0
166 @body = []
167 @charset = nil
168 @content_type = nil
169
170 @request = @template = nil
171 end
172 end
173
174 class TestSession < ActionDispatch::Session::AbstractStore::SessionHash #:nodoc:
175 DEFAULT_OPTIONS = ActionDispatch::Session::AbstractStore::DEFAULT_OPTIONS
176
177 def initialize(session = {})
178 replace(session.stringify_keys)
179 @loaded = true
180 end
181 end
182
6e75455 Pratik Merge docrails changes
lifo authored
183 # Superclass for ActionController functional tests. Functional tests allow you to
184 # test a single controller action per test method. This should not be confused with
185 # integration tests (see ActionController::IntegrationTest), which are more like
186 # "stories" that can involve multiple controllers and mutliple actions (i.e. multiple
187 # different HTTP requests).
0432d15 Pratik Merge with docrails.
lifo authored
188 #
6e75455 Pratik Merge docrails changes
lifo authored
189 # == Basic example
190 #
191 # Functional tests are written as follows:
192 # 1. First, one uses the +get+, +post+, +put+, +delete+ or +head+ method to simulate
193 # an HTTP request.
194 # 2. Then, one asserts whether the current state is as expected. "State" can be anything:
195 # the controller's HTTP response, the database contents, etc.
196 #
197 # For example:
198 #
199 # class BooksControllerTest < ActionController::TestCase
200 # def test_create
201 # # Simulate a POST response with the given HTTP parameters.
202 # post(:create, :book => { :title => "Love Hina" })
203 #
204 # # Assert that the controller tried to redirect us to
205 # # the created book's URI.
206 # assert_response :found
207 #
208 # # Assert that the controller really put the book in the database.
209 # assert_not_nil Book.find_by_title("Love Hina")
0432d15 Pratik Merge with docrails.
lifo authored
210 # end
211 # end
212 #
6e75455 Pratik Merge docrails changes
lifo authored
213 # == Special instance variables
214 #
215 # ActionController::TestCase will also automatically provide the following instance
216 # variables for use in the tests:
217 #
218 # <b>@controller</b>::
219 # The controller instance that will be tested.
220 # <b>@request</b>::
221 # An ActionController::TestRequest, representing the current HTTP
222 # request. You can modify this object before sending the HTTP request. For example,
223 # you might want to set some session properties before sending a GET request.
224 # <b>@response</b>::
225 # An ActionController::TestResponse object, representing the response
226 # of the last HTTP response. In the above example, <tt>@response</tt> becomes valid
227 # after calling +post+. If the various assert methods are not sufficient, then you
228 # may use this object to inspect the HTTP response in detail.
229 #
230 # (Earlier versions of Rails required each functional test to subclass
231 # Test::Unit::TestCase and define @controller, @request, @response in +setup+.)
0432d15 Pratik Merge with docrails.
lifo authored
232 #
6e75455 Pratik Merge docrails changes
lifo authored
233 # == Controller is automatically inferred
0432d15 Pratik Merge with docrails.
lifo authored
234 #
6e75455 Pratik Merge docrails changes
lifo authored
235 # ActionController::TestCase will automatically infer the controller under test
236 # from the test class name. If the controller cannot be inferred from the test
e033b5d Pratik Merge docrails
lifo authored
237 # class name, you can explicitly set it with +tests+.
0432d15 Pratik Merge with docrails.
lifo authored
238 #
239 # class SpecialEdgeCaseWidgetsControllerTest < ActionController::TestCase
240 # tests WidgetController
241 # end
d355921 Jeremy Kemper Remove controller assertions from Test::Unit::TestCase. Use ActionContro...
jeremy authored
242 #
243 # == Testing controller internals
244 #
245 # In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions
246 # can be used against. These collections are:
247 #
248 # * assigns: Instance variables assigned in the action that are available for the view.
249 # * session: Objects being saved in the session.
250 # * flash: The flash objects currently in the session.
251 # * cookies: Cookies being sent to the user on this request.
252 #
253 # These collections can be used just like any other hash:
254 #
255 # assert_not_nil assigns(:person) # makes sure that a @person instance variable was set
256 # assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
257 # assert flash.empty? # makes sure that there's nothing in the flash
258 #
259 # For historic reasons, the assigns hash uses string-based keys. So assigns[:person] won't work, but assigns["person"] will. To
260 # appease our yearning for symbols, though, an alternative accessor has been devised using a method call instead of index referencing.
261 # So assigns(:person) will work just like assigns["person"], but again, assigns[:person] will not work.
262 #
263 # On top of the collections, you have the complete url that a given action redirected to available in redirect_to_url.
264 #
265 # For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
266 # action call which can then be asserted against.
267 #
268 # == Manipulating the request collections
269 #
270 # The collections described above link to the response, so you can test if what the actions were expected to do happened. But
271 # sometimes you also want to manipulate these collections in the incoming request. This is really only relevant for sessions
272 # and cookies, though. For sessions, you just do:
273 #
274 # @request.session[:key] = "value"
3b317b7 Joshua Peek Switch to Rack::Response#set_cookie instead of using CGI::Cookie to buil...
josh authored
275 # @request.cookies["key"] = "value"
d355921 Jeremy Kemper Remove controller assertions from Test::Unit::TestCase. Use ActionContro...
jeremy authored
276 #
277 # == Testing named routes
278 #
279 # If you're using named routes, they can be easily tested using the original named routes' methods straight in the test case.
280 # Example:
281 #
282 # assert_redirected_to page_url(:title => 'foo')
2cc0cac Michael Koziarski Introduce TestCase subclasses for testing rails applications allowing te...
NZKoz authored
283 class TestCase < ActiveSupport::TestCase
ee395fe Joshua Peek TestProcess belongs in AD
josh authored
284 include ActionDispatch::TestProcess
947f86c Modify assert_template to use instrumentation
Carlhuda authored
285 include ActionController::TemplateAssertions
35fa007 Jeremy Kemper Include process methods in ActionController::TestCase only. No need to a...
jeremy authored
286
542ddde Joshua Peek Move helpers specific to functional tests out of TestProcess into AC::Te...
josh authored
287 # Executes a request simulating GET HTTP method and set/volley the response
288 def get(action, parameters = nil, session = nil, flash = nil)
289 process(action, parameters, session, flash, "GET")
290 end
291
292 # Executes a request simulating POST HTTP method and set/volley the response
293 def post(action, parameters = nil, session = nil, flash = nil)
294 process(action, parameters, session, flash, "POST")
295 end
296
297 # Executes a request simulating PUT HTTP method and set/volley the response
298 def put(action, parameters = nil, session = nil, flash = nil)
299 process(action, parameters, session, flash, "PUT")
300 end
301
302 # Executes a request simulating DELETE HTTP method and set/volley the response
303 def delete(action, parameters = nil, session = nil, flash = nil)
304 process(action, parameters, session, flash, "DELETE")
305 end
306
307 # Executes a request simulating HEAD HTTP method and set/volley the response
308 def head(action, parameters = nil, session = nil, flash = nil)
309 process(action, parameters, session, flash, "HEAD")
310 end
311
312 def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
313 @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
314 @request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
315 returning __send__(request_method, action, parameters, session, flash) do
316 @request.env.delete 'HTTP_X_REQUESTED_WITH'
317 @request.env.delete 'HTTP_ACCEPT'
318 end
319 end
320 alias xhr :xml_http_request
321
322 def process(action, parameters = nil, session = nil, flash = nil, http_method = 'GET')
323 # Sanity check for required instance variables so we can give an
324 # understandable error message.
cdf8c35 Joshua Peek Consistent routing language
josh authored
325 %w(@routes @controller @request @response).each do |iv_name|
542ddde Joshua Peek Move helpers specific to functional tests out of TestProcess into AC::Te...
josh authored
326 if !(instance_variable_names.include?(iv_name) || instance_variable_names.include?(iv_name.to_sym)) || instance_variable_get(iv_name).nil?
327 raise "#{iv_name} is nil: make sure you set it in your test's setup method."
328 end
329 end
330
331 @request.recycle!
332 @response.recycle!
333 @controller.response_body = nil
334 @controller.formats = nil
335 @controller.params = nil
336
337 @html_document = nil
338 @request.env['REQUEST_METHOD'] = http_method
339
340 parameters ||= {}
cdf8c35 Joshua Peek Consistent routing language
josh authored
341 @request.assign_parameters(@routes, @controller.class.name.underscore.sub(/_controller$/, ''), action.to_s, parameters)
542ddde Joshua Peek Move helpers specific to functional tests out of TestProcess into AC::Te...
josh authored
342
343 @request.session = ActionController::TestSession.new(session) unless session.nil?
ead93c5 Joshua Peek Move Flash into middleware
josh authored
344 @request.session["flash"] = @request.flash.update(flash || {})
345 @request.session["flash"].sweep
542ddde Joshua Peek Move helpers specific to functional tests out of TestProcess into AC::Te...
josh authored
346
347 @controller.request = @request
348 @controller.params.merge!(parameters)
349 build_request_uri(action, parameters)
350 Base.class_eval { include Testing }
351 @controller.process_with_new_base_test(@request, @response)
ead93c5 Joshua Peek Move Flash into middleware
josh authored
352 @request.session.delete('flash') if @request.session['flash'].blank?
542ddde Joshua Peek Move helpers specific to functional tests out of TestProcess into AC::Te...
josh authored
353 @response
354 end
355
7b3b7cb Joshua Peek Move generic assertions into ActionDispatch
josh authored
356 include ActionDispatch::Assertions
d355921 Jeremy Kemper Remove controller assertions from Test::Unit::TestCase. Use ActionContro...
jeremy authored
357
dc2d693 Added ActionController::TestCase#rescue_action_in_public! to control whe...
David Heinemeier Hansson authored
358 # When the request.remote_addr remains the default for testing, which is 0.0.0.0, the exception is simply raised inline
359 # (bystepping the regular exception handling from rescue_action). If the request.remote_addr is anything else, the regular
360 # rescue_action process takes place. This means you can test your rescue_action code by setting remote_addr to something else
361 # than 0.0.0.0.
362 #
363 # The exception is stored in the exception accessor for further inspection.
139b924 Jeremy Kemper * Continue evolution toward ActiveSupport::TestCase and friends. #10679...
jeremy authored
364 module RaiseActionExceptions
34a37ea Jeremy Kemper Workaround jruby issue with protected module attr_accessor showing up as...
jeremy authored
365 def self.included(base)
366 base.class_eval do
367 attr_accessor :exception
368 protected :exception, :exception=
369 end
370 end
dc2d693 Added ActionController::TestCase#rescue_action_in_public! to control whe...
David Heinemeier Hansson authored
371
34a37ea Jeremy Kemper Workaround jruby issue with protected module attr_accessor showing up as...
jeremy authored
372 protected
347db97 Jeremy Kemper Take care not to mix in public methods
jeremy authored
373 def rescue_action_without_handler(e)
374 self.exception = e
375
376 if request.remote_addr == "0.0.0.0"
377 raise(e)
378 else
379 super(e)
380 end
dc2d693 Added ActionController::TestCase#rescue_action_in_public! to control whe...
David Heinemeier Hansson authored
381 end
139b924 Jeremy Kemper * Continue evolution toward ActiveSupport::TestCase and friends. #10679...
jeremy authored
382 end
383
384 setup :setup_controller_request_and_response
385
2cc0cac Michael Koziarski Introduce TestCase subclasses for testing rails applications allowing te...
NZKoz authored
386 @@controller_class = nil
139b924 Jeremy Kemper * Continue evolution toward ActiveSupport::TestCase and friends. #10679...
jeremy authored
387
2cc0cac Michael Koziarski Introduce TestCase subclasses for testing rails applications allowing te...
NZKoz authored
388 class << self
0432d15 Pratik Merge with docrails.
lifo authored
389 # Sets the controller class name. Useful if the name can't be inferred from test class.
390 # Expects +controller_class+ as a constant. Example: <tt>tests WidgetController</tt>.
2cc0cac Michael Koziarski Introduce TestCase subclasses for testing rails applications allowing te...
NZKoz authored
391 def tests(controller_class)
392 self.controller_class = controller_class
393 end
394
395 def controller_class=(new_class)
c82e8e1 Jeremy Kemper Move controller assertions from base TestCase to AC:: and AV::TestCase
jeremy authored
396 prepare_controller_class(new_class) if new_class
2cc0cac Michael Koziarski Introduce TestCase subclasses for testing rails applications allowing te...
NZKoz authored
397 write_inheritable_attribute(:controller_class, new_class)
398 end
399
400 def controller_class
401 if current_controller_class = read_inheritable_attribute(:controller_class)
402 current_controller_class
403 else
139b924 Jeremy Kemper * Continue evolution toward ActiveSupport::TestCase and friends. #10679...
jeremy authored
404 self.controller_class = determine_default_controller_class(name)
2cc0cac Michael Koziarski Introduce TestCase subclasses for testing rails applications allowing te...
NZKoz authored
405 end
406 end
407
408 def determine_default_controller_class(name)
409 name.sub(/Test$/, '').constantize
410 rescue NameError
c82e8e1 Jeremy Kemper Move controller assertions from base TestCase to AC:: and AV::TestCase
jeremy authored
411 nil
2cc0cac Michael Koziarski Introduce TestCase subclasses for testing rails applications allowing te...
NZKoz authored
412 end
413
414 def prepare_controller_class(new_class)
139b924 Jeremy Kemper * Continue evolution toward ActiveSupport::TestCase and friends. #10679...
jeremy authored
415 new_class.send :include, RaiseActionExceptions
2cc0cac Michael Koziarski Introduce TestCase subclasses for testing rails applications allowing te...
NZKoz authored
416 end
417 end
418
139b924 Jeremy Kemper * Continue evolution toward ActiveSupport::TestCase and friends. #10679...
jeremy authored
419 def setup_controller_request_and_response
c82e8e1 Jeremy Kemper Move controller assertions from base TestCase to AC:: and AV::TestCase
jeremy authored
420 @request = TestRequest.new
cff3ecc Pratik Allow using named routes in ActionController::TestCase before any reques...
lifo authored
421 @response = TestResponse.new
b47c76b Eloy Durán Make sure named routes with parameters can be used in tests before a req...
alloy authored
422
c82e8e1 Jeremy Kemper Move controller assertions from base TestCase to AC:: and AV::TestCase
jeremy authored
423 if klass = self.class.controller_class
424 @controller ||= klass.new rescue nil
425 end
426
5e0a05b Tweak the semantic of various URL related methods of ActionDispatch::Req...
Carlhuda authored
427 @request.env.delete('PATH_INFO')
428
c82e8e1 Jeremy Kemper Move controller assertions from base TestCase to AC:: and AV::TestCase
jeremy authored
429 if @controller
430 @controller.request = @request
431 @controller.params = {}
432 end
2cc0cac Michael Koziarski Introduce TestCase subclasses for testing rails applications allowing te...
NZKoz authored
433 end
e033b5d Pratik Merge docrails
lifo authored
434
dc2d693 Added ActionController::TestCase#rescue_action_in_public! to control whe...
David Heinemeier Hansson authored
435 # Cause the action to be rescued according to the regular rules for rescue_action when the visitor is not local
436 def rescue_action_in_public!
437 @request.remote_addr = '208.77.188.166' # example.com
438 end
542ddde Joshua Peek Move helpers specific to functional tests out of TestProcess into AC::Te...
josh authored
439
7db80f8 Joshua Peek Move AC::UrlRewriter onto route set
josh authored
440 private
441 def build_request_uri(action, parameters)
442 unless @request.env["PATH_INFO"]
443 options = @controller.__send__(:url_options).merge(parameters)
444 options.update(
445 :only_path => true,
446 :action => action,
447 :relative_url_root => nil,
448 :_path_segments => @request.symbolized_path_parameters)
449
cdf8c35 Joshua Peek Consistent routing language
josh authored
450 url, query_string = @routes.url_for(options).split("?", 2)
7db80f8 Joshua Peek Move AC::UrlRewriter onto route set
josh authored
451
452 @request.env["SCRIPT_NAME"] = @controller.config.relative_url_root
453 @request.env["PATH_INFO"] = url
454 @request.env["QUERY_STRING"] = query_string || ""
455 end
542ddde Joshua Peek Move helpers specific to functional tests out of TestProcess into AC::Te...
josh authored
456 end
5e0a05b Tweak the semantic of various URL related methods of ActionDispatch::Req...
Carlhuda authored
457 end
6e75455 Pratik Merge docrails changes
lifo authored
458 end
Something went wrong with that request. Please try again.