## # ViewTestCase allows views to be tested independent of their controllers. # Testcase implementors must set up the instance variables the view needs to # render itself. # # = Features # # * Allows testing of individual AJAX templates. # * Allows testing of individual partials. # * Large library of helful assertions. # # = Naming # # The test class must be named after your controller class name, so if you're # testing views for the +RouteController+ you would name your test case # +RouteViewTest+. The test case will expect to find your view files in # app/views/route. # # The test names should be in the form of +test_view_edgecase+ where 'view' # corresponds to the name of the view file, and 'edgecase' describes the # scenario you are testing. # # If you are testing a view file named 'show.rhtml' your test should be named # +test_show+. If your view is behaves differently depending upon its # parameters then you can make the test name descriptive like # +test_show_photos+ and +test_show_no_photos+. # # = Examples # # == Typical View Test # # class RouteViewTest < Test::Rails::ViewTestCase # # fixtures :users, :routes, :points, :photos # # def test_delete # # Set up instance variables for template # assigns[:loggedin_user] = users(:herbert) # assigns[:route] = routes(:work) # # # render template for the delete action in RouteController # render # # # assert that there's a form with an action of "/route/destroy" # assert_form form_url, :post do # # with a hidden id field # assert_input :hidden, :id # # And a submit button that says 'Delete!' # assert_submit 'Delete!' # end # # # And a link back to the route so you don't delete it # assert_links_to "/route/show/#{routes(:work).id}", 'No, I do not!' # end # # end # # == Typical Layout Test # # require 'test/test_helper' # # # Create a dummy controller for layout views. This lets the setup use the # # right path with minimum fuss. # class LayoutsController < ApplicationController; end # # class LayoutsViewTest < Test::Rails::ViewTestCase # # fixtures :users, :routes, :points, :photos # # def test_default # # Template set-up # @request.request_uri = '/foo' # assigns[:action_title] = 'Hello & Goodbye' # # # Render an empty string with the 'application' layout. # render :text => '', :layout => 'application' # # # Assert content just like a regular view test. # assert_links_to '/', 'Home' # assert_links_to '/user', 'Login' # deny_links_to '/user/logout', 'Logout' # assert_title 'Hello & Goodbye' # assert_h 1, 'Hello & Goodbye' # end # # end # # = Deprecated Features # # Form assertions are now using assert_select, so you don't need to pass URLs # around everywhere and can instead use a block. (See above example). # # The form assertions will still work using the old syntax, but in a future # release they will give warnings, then will be removed. class Test::Rails::ViewTestCase < Test::Rails::FunctionalTestCase self.use_transactional_fixtures = true self.use_instantiated_fixtures = false ## # Sets up the test case. def setup return if self.class == Test::Rails::ViewTestCase @path_parameters ||= {} klass_name = self.class.name.sub(/View/, 'Controller') @controller_class_name ||= klass_name.sub 'Test', '' super @ivar_proxy = Test::Rails::IvarProxy.new @controller # these go here so that flash and session work as they should. @controller.send :initialize_template_class, @response @controller.send :assign_shortcuts, @request, @response assigns[:session] = @controller.session @controller.class.send :public, :flash # make flash accessible to the test end ## # Allows the view instance variables to be set like flash: # # test: # def test_show # assigns[:route] = routes(:work) def assigns @ivar_proxy end ## # Renders the template. The template is determined from the test name. If # you have multiple tests for the same view render will try to Do The Right # Thing and remove parts of the name looking for the template file. # # By default, render has the added option :layout => false, # so if want to test behavior in your layout add :layout => true. # # The action can be forced by using the options: # # render :action => 'new' # # render :template => 'profile/index' # # A test's path parameters may be overridden, allowing routes with # additional parameters to work. # # == Working with Routes # # By default, a view tests sets the controller and action of a test to the # controller name and action name for the test. This may be overriden. # # A test involving routes like: # # map.workspace '/users/:owner/workspace/:action', # :controller => 'workspace', :action => 'workspace' # # Can be invoked by setting @path_parameters like this: # # def test__app_entry # @path_parameters[:owner] = 'bob' # @path_parameters[:action] = 'apps' # # render :partial => 'apps/app_entry' # # # ... # end # # == View Lookup # # render strips off words trailing an _ in the test name one at a time until # it finds a matching action. It tries the extensions 'rhtml', 'rxml', # 'rjs', and 'mab' in order for each action until a view is found. # # With this test case: # # class RouteViewTest < Test::Rails::ViewTestCase # def test_show_photos # render # end # def test_show_no_photos # render # end # end # # In test_show_photos, render will look for: # * app/views/route/show_photos.rhtml # * app/views/route/show_photos.rxml # * app/views/route/show_photos.rjs # * app/views/route/show_photos.mab # * app/views/route/show.[...] # # And in test_show_no_photos, render will look for: # * app/views/route/show_no_photos.rhtml # * app/views/route/show_no_photos.rxml # * app/views/route/show_no_photos.rjs # * app/views/route/show_no_photos.mab # * app/views/route/show_no.[...] # * app/views/route/show.[...] # # If a view cannot be found the test will flunk. def render(options = {}, deprecated_status = nil) @action_name = action_name caller[0] if options.empty? assigns[:action_name] = @action_name default_path_parameters = { :controller => @controller.controller_name, :action => @action_name } path_parameters = default_path_parameters.merge(@path_parameters) @request.path_parameters = path_parameters defaults = { :layout => false } options = defaults.merge options if Test::Rails.rails_version >= Test::Rails.v1_2 then @controller.send :params=, @request.parameters else @controller.instance_variable_set :@params, @request.parameters end @controller.send :initialize_current_url current_url = URI.parse @controller.url_for @request.request_uri = current_url.request_uri # Rails 1.0 @controller.send :assign_names rescue nil @controller.send :fire_flash rescue nil # Rails 1.1 @controller.send :forget_variables_added_to_assigns rescue nil # Do the render options[:TR_force] = true @controller.render options, deprecated_status # Rails 1.1 @controller.send :process_cleanup rescue nil end ## # Asserts that there is an error on +field+ of type +type+. def assert_error_on(field, type) error_message = ActiveRecord::Errors.default_error_messages[type] assert_select "div.errorExplanation li", :text => /^#{field} #{error_message}/i end ## # A wrapper assert that calls both assert_input and assert_label. # # view: # <%= start_form_tag :controller => 'game', :action => 'save' %> # # <% text_field 'game', 'amount' %> # # test: # assert_field '/game/save', :text, :game, :amount def assert_field(*args) form_action, type, model, column, value = Symbol === args.first ? [nil, *args] : args if form_action then # HACK deprecate assert_input form_action, type, "#{model}[#{column}]", value assert_label form_action, "#{model}_#{column}" else assert_input type, "#{model}[#{column}]", value assert_label "#{model}_#{column}" end end ## # Asserts that there is a form whose action is +form_action+. Optionally, # +method+ and +enctype+ may be specified. If a block is given, assert_form # behaves like assert_select, so assert_input and friends may be scoped to # the selected form. # # view: # <%= start_form_tag :action => 'create_file' %> # # ... # # test: # assert_form '/game/save' # # or: # assert_form '/game/save' do # # ... # end def assert_form(form_action, method = nil, enctype = nil, &block) selector = "form[action='#{form_action}']" selector << "[method='#{method}']" if method selector << "[enctype='#{enctype}']" if enctype assert_select selector, &block end ## # Asserts a hN tag of level +level+ exists and contains +content+. # # view: #