Class: Test::Rails::ViewTestCase

Inherits:
FunctionalTestCase show all
Defined in:
lib/test/rails/view_test_case.rb

Overview

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 helpful 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 &amp; Goodbye'
    assert_h 1, 'Hello &amp; 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.

Instance Attribute Summary

Attributes inherited from FunctionalTestCase

#flash, #session

Instance Method Summary collapse

Instance Method Details

#action_name(test) ⇒ Object

Returns the action_name based on a backtrace line passed in as test.



576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
# File 'lib/test/rails/view_test_case.rb', line 576

def action_name(test)
  orig_name = test = test.sub(/.*in `test_(.*)'/, '\1')
  controller = @controller.class.name.sub('Controller', '').underscore

  extensions = %w[rhtml rxml rjs mab]

  while test =~ /_/ do
    return test if extensions.any? { |ext| File.file? "app/views/#{controller}/#{test}.#{ext}" }

    test = test.sub(/_[^_]+$/, '')
  end

  return test if extensions.any? { |ext| File.file? "app/views/#{controller}/#{test}.#{ext}" }

  flunk "Couldn't find view for test_#{orig_name}"
end

#assert_error_on(field, type) ⇒ Object

Asserts that there is an error on field of type type.



247
248
249
250
251
# File 'lib/test/rails/view_test_case.rb', line 247

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

#assert_field(*args) ⇒ Object

A wrapper assert that calls both assert_input and assert_label.

view:

<%= start_form_tag :controller => 'game', :action => 'save' %>
<label for="game_amount">Amount:</label>
<% text_field 'game', 'amount' %>

test:

assert_field '/game/save', :text, :game, :amount


264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/test/rails/view_test_case.rb', line 264

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

#assert_form(form_action, method = nil, enctype = nil, &block) ⇒ Object

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


295
296
297
298
299
300
# File 'lib/test/rails/view_test_case.rb', line 295

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

#assert_h(level, content) ⇒ Object

Asserts a hN tag of level level exists and contains content.

view:

<h3>Recent Builds</h3>

test:

assert_h 3, 'Recent Builds'


311
312
313
# File 'lib/test/rails/view_test_case.rb', line 311

def assert_h(level, content)
  assert_select "h#{level}", :text => content
end

#assert_image(src) ⇒ Object

Asserts that an image exists with a src of src.

view:

<img src="/images/bucket.jpg" alt="Bucket">

test:

assert_image '/images/bucket.jpg'


324
325
326
# File 'lib/test/rails/view_test_case.rb', line 324

def assert_image(src)
  assert_select "img[src='#{src}']"
end

#assert_input(*args) ⇒ Object

Asserts that an input element of type with a name of name, and optionally a value of value exists.

view:

<%= text_field 'game', 'amount' %>

test:

assert_input :text, "game[amount]"

Raises:

  • (ArgumentError)


338
339
340
341
342
343
344
345
346
347
# File 'lib/test/rails/view_test_case.rb', line 338

def assert_input(*args)
  action, type, name, value = Symbol === args.first ? [nil, *args] : args

  raise ArgumentError, 'supply type and name' if type.nil? or name.nil?

  input_selector = "input[type='#{type}'][name='#{name}']"
  input_selector << "[value='#{value}']" if value

  assert_select_in_form action do assert_select input_selector end
end

#assert_label(*args) ⇒ Object

Asserts that a label with a for attribute of for_attribute exists.

view:

<%= start_form_tag :controller => 'game', :action => 'save' %>
<label for="game_amount">Amount:</label>

test:

assert_label 'game_amount'

Raises:

  • (ArgumentError)


359
360
361
362
363
364
365
366
367
# File 'lib/test/rails/view_test_case.rb', line 359

def assert_label(*args)
  action, for_attribute = args.length == 1 ? [nil, *args] : args

  raise ArgumentError, 'supply for_attribute' if for_attribute.nil?

  label_selector = "label[for='#{for_attribute}']"

  assert_select_in_form action do assert_select label_selector end
end

Asserts that there is an anchor tag with an href of href that optionally has content.

view:

<%= link_to 'drbrain', :model => user %>

test:

assert_links_to '/players/show/1', 'drbrain'


379
380
381
# File 'lib/test/rails/view_test_case.rb', line 379

def assert_links_to(href, content = nil)
  assert_select(*links_to_options_for(href, content))
end

#assert_multipart_form(form_action, &block) ⇒ Object

Asserts that there is a form using the ‘POST’ method whose action is form_action and uses the multipart content type. If passed a block, works like assert_form.

view:

<%= start_form_tag({ :action => 'create_file' }, :multipart => true) %>

test:

assert_multipart_form '/game/save'


411
412
413
# File 'lib/test/rails/view_test_case.rb', line 411

def assert_multipart_form(form_action, &block)
  assert_form(form_action, :post, 'multipart/form-data', &block)
end

#assert_post_form(form_action, &block) ⇒ Object

Asserts that there is a form using the ‘POST’ method whose action is form_action. If passed a block, works like assert_form.

view:

<%= start_form_tag :action => 'create_file' %>

test:

assert_post_form '/game/save'


425
426
427
# File 'lib/test/rails/view_test_case.rb', line 425

def assert_post_form(form_action, &block)
  assert_form(form_action, :post, &block)
end

#assert_select_in_form(action, &block) ⇒ Object

Utility method for compatibility with old-style assert_tag form assertions.



556
557
558
559
560
561
562
# File 'lib/test/rails/view_test_case.rb', line 556

def assert_select_in_form(action, &block) # :nodoc:
  if action then
    assert_form(action, &block)
  else
    block.call
  end
end

#assert_select_tag(*args) ⇒ Object

Asserts that a select element with a name of “model[column]” and options with specified names and values exists.

view:

<%= collection_select :game, :location_id, @locations, :id, :name %>

test:

assert_select_tag :game, :location_id, 'Ballet' => 1, 'Guaymas' => 2


439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
# File 'lib/test/rails/view_test_case.rb', line 439

def assert_select_tag(*args)
  action, model, column, options = Symbol === args.first ? [nil, *args] : args

  assert_kind_of Hash, options, "options needs to be a Hash"
  deny options.empty?, "options must not be empty"

  select_selector = "select[name='#{model}[#{column}]']"

  options.each do |option_name, option_value|
    option_selector = "option[value='#{option_value}']"
    selector = "#{select_selector} #{option_selector}"

    assert_select_in_form action do
      assert_select selector, :text => option_name
    end
  end
end

#assert_submit(*args) ⇒ Object

Asserts that a submit element with a value of value exists.

view:

<input type="submit" value="Create!" %>

test:

assert_submit 'Create!'


466
467
468
469
470
471
472
# File 'lib/test/rails/view_test_case.rb', line 466

def assert_submit(*args)
  action, value = args.length == 1 ? [nil, *args] : args

  submit_selector = "input[type='submit'][value='#{value}']"

  assert_select_in_form action do assert_select submit_selector end
end

#assert_tag_in_form(form_action, options) ⇒ Object

Asserts that a form with form_action has a descendent that matches options exists.

Typically this is not used directly in tests. Instead use it to build expressive tests that assert which fields are in what form.

view:

<%= start_form_tag :action => 'save' %>
[...]

test:

assert_tag_in_form '/route/save', :tag => 'table'


488
489
490
491
# File 'lib/test/rails/view_test_case.rb', line 488

def assert_tag_in_form(form_action, options)
  assert_tag :tag => 'form', :attributes => { :action => form_action },
               :descendant => options
end

#assert_text_area(*args) ⇒ Object Also known as: assert_textarea

Asserts that a textarea with name name and optionally value exists.

view:

<%= text_area 'post', 'body' %>

test:

assert_text_area 'post[body]'

view:

<textarea id="post_body" name="post[body]">
<%= @post.body %>
</textarea>

test:

assert_text_area 'post[body]', posts(:post).body

Raises:

  • (ArgumentError)


510
511
512
513
514
515
516
517
518
519
# File 'lib/test/rails/view_test_case.rb', line 510

def assert_text_area(*args)
  action, name, value = args.first !~ /\A\// ? [nil, *args] : args

  raise ArgumentError, 'supply name' if name.nil?

  text_area_selector = ["textarea[name='#{name}']"]
  text_area_selector << { :text => value } if value

  assert_select_in_form action do assert_select(*text_area_selector) end
end

#assert_title(title) ⇒ Object

Asserts that a title with title exists.

view:

<title>some content</title>

test:

assert_title 'some content'


532
533
534
# File 'lib/test/rails/view_test_case.rb', line 532

def assert_title(title)
  assert_select 'title', :text => title
end

#assignsObject

Allows the view instance variables to be set like flash:

test:

def test_show
  assigns[:route] = routes(:work)


128
129
130
# File 'lib/test/rails/view_test_case.rb', line 128

def assigns
  @ivar_proxy
end

Denies the existence of an anchor tag with an href of href and optionally content.

view (for /players/show/1):

<%= link_to_unless_current 'drbrain', :model => user %>

test:

deny_links_to '/players/show/1'


393
394
395
396
397
398
# File 'lib/test/rails/view_test_case.rb', line 393

def deny_links_to(href, content = nil)
  selector, options = links_to_options_for(href, content)
  options[:count] = 0

  assert_select selector, options
end

#deny_select(selector) ⇒ Object

Opposite of assert_select.



539
540
541
# File 'lib/test/rails/view_test_case.rb', line 539

def deny_select(selector)
  assert_select selector, false
end

Creates an assertion options hash for href and content.



567
568
569
570
571
# File 'lib/test/rails/view_test_case.rb', line 567

def links_to_options_for(href, content = nil)
  selector = "a[href='#{href}']"
  equality = content ? { :text => content } : {}
  return selector, equality
end

#render(options = {}, *rest_args) ⇒ Object

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:

If a view cannot be found the test will flunk.



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/test/rails/view_test_case.rb', line 204

def render(options = {}, *rest_args)
  @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.send(:render, options, *rest_args)

  # Rails 1.1
  @controller.send :process_cleanup rescue nil
end

#setupObject

Sets up the test case.



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/test/rails/view_test_case.rb', line 101

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

#util_make_paginator(item_count, items_per_page, page_number) ⇒ Object

Creates a new Paginator that uses the current controller. item_count, items_per_page and page_number are passed straight through.



547
548
549
550
# File 'lib/test/rails/view_test_case.rb', line 547

def util_make_paginator(item_count, items_per_page, page_number)
  ActionController::Pagination::Paginator.new(@controller, item_count,
                                              items_per_page, page_number)
end