Ruby on Rails
HowtoFunctionalTest (Version #23)

For a more thorough introduction to testing see also: A Guide to Testing the Rails

Functional tests are intended to test multiple layers of the applications. You can either do functional testing from the controller and down or from the view and down. Both approaches use the functional test stubs created for each controller when using script/new_controller.

Controller-driven functional tests

Here are two test cases that are used to test authentication on Basecamp:


def test_failing_authenticate
  process 'authenticate', {
                          "user_name" => "nop",
                          "password"  => ""
                          }
  
  assert_flash_equal(
    "The username and/or password you entered is invalid.", 
    "alert"
  )
 
  assert_redirect_url "http://37signals.basecamp.com/login/"
end
 
def test_succesful_firm_authentication
  process 'authenticate', {
                          "user_name" => "jf",
                          "password"  => "chimark"
                          }
 
  assert_redirect_url "http://37signals.basecamp.com/clients/"
 
  assert_equal(
    Person.find(1).full_name, response.session["person"].full_name
  )
end

The `process` method fires up the controller with a given action(the first parameter) as well as optional request parameters (eg. form post variables)

Then once the response has been generated, you can interrogate it and see if all went well.

View-driven functional tests

It’s a good idea to require that the views emit well-formed, valid XHTML. This allows you to analyze the results with an XML parser.


def test_show_conversation
  ...
  response = ForumController.process_test(@request)
  ...
  # parse the resulting HTML into a REXML::Document object
  # if the output is not a well-formed XML, an exception is thrown
  output = Document.new(response.body)
  # and here you can use REXML APIs, including XPath
  # to retrieve pieces of data from the XML

end

Here is one example how REXML::XPath can be used to test template output:


...
require 'rexml/document'
...
  def test_show
    @request.path   = '/test/show'
    @request.action = 'show'
    @request.request_parameters['id'] = '1'

    response = ConversationController.process_test(@request)

    # Parse the response body
    xml = nil
    assert_nothing_thrown { xml = REXML::Document.new(response.body) }
    
    # Retrieve all hyperlinks from somewhere deep inside the response
    title_hrefs = REXML::XPath.match(xml,
        '/html/body/table/tr[2]/td/table/tr[1]/td/a')

    # Check that there are three hyperlinks...
    assert_equal 3, title_hrefs.size

    # ... and they point where they should
    href_urls = title_hrefs.map {|element| element.attribute('href').value }.sort
    assert_equal ['/conversation/show?id=1', '/forum/?id=1',
        '/message/new?conv_id=1'], href_urls
  end

Also, for each action I write at least one test that writes the response body to a file in some temporary directory. I can then use tidy on all saved HTML files to clean out the warnings.

AlexeyVerkhovsky

——

Question 1: what is the “best” way to validate view output against XHTML DTD?

Answer: There is a ruby-tidy gem that you can use to validate XHTML rather than, as suggested above, write it to a file and use the tidy commandline. You’ll need to complile/install the regular C version of tidy. The Tidy gem just loads the C library. See: http://tidy.rubyforge.org/

Question 2: If I wanted to test that the view displayed a particular section if a certain @flash item was set, how do you set up the @flash object in the request object? I tried assigning a hash to request.session‘flash’, but that doesn’t seem to work.

Answer: The request takes four parameters, the final being the flash parameters. for example “get :index, nil, nil, { :what => "ever” }

Comment: I’ve been playing a bit with the “from the view and down” approach to functional testing. Here are my first impressions .

——

Testing a Controller that requires Login

Here’s the scenario:

  • the CategoriesController requires an admin to be logged in before allowing access to any of its actions
  • the UserLoginController is the controller that handles authenticating admins

Question: How do you test the CategoriesController?

Answer:

Step 1

Create a login function called login in test_helper.rb, that automatically logs in a user.

Step 2

In the setup function of CategoriesController, include the following:


    # to deal with the Categories controller we need to login first.
    # We do this by switching the current controller @controller
    # to the UserLoginController, logging in, then switching the
    # current controller to CategoriesController.
    @controller = UserLoginController.new
    login # in test_helper.rb
    @controller = CategoriesController.new

Since setup is called before each test, a user will be logged in before performing any actions in the test.

Step 3 (optional)

To be able to set which user logs in, you can add the following to test_helper.rb. You can then call login_first from any of your tests.


def login_first(username, password)
  controller = @controller
  @controller = UserLoginController.new
  login(username,password) # also in test_helper.rb
  @controller = controller
end

Of course, you can also call login_first in setup à la Step 2 so you don’t need to include it in each test for a given controller.

category:Howto

For a more thorough introduction to testing see also: A Guide to Testing the Rails

Functional tests are intended to test multiple layers of the applications. You can either do functional testing from the controller and down or from the view and down. Both approaches use the functional test stubs created for each controller when using script/new_controller.

Controller-driven functional tests

Here are two test cases that are used to test authentication on Basecamp:


def test_failing_authenticate
  process 'authenticate', {
                          "user_name" => "nop",
                          "password"  => ""
                          }
  
  assert_flash_equal(
    "The username and/or password you entered is invalid.", 
    "alert"
  )
 
  assert_redirect_url "http://37signals.basecamp.com/login/"
end
 
def test_succesful_firm_authentication
  process 'authenticate', {
                          "user_name" => "jf",
                          "password"  => "chimark"
                          }
 
  assert_redirect_url "http://37signals.basecamp.com/clients/"
 
  assert_equal(
    Person.find(1).full_name, response.session["person"].full_name
  )
end

The `process` method fires up the controller with a given action(the first parameter) as well as optional request parameters (eg. form post variables)

Then once the response has been generated, you can interrogate it and see if all went well.

View-driven functional tests

It’s a good idea to require that the views emit well-formed, valid XHTML. This allows you to analyze the results with an XML parser.


def test_show_conversation
  ...
  response = ForumController.process_test(@request)
  ...
  # parse the resulting HTML into a REXML::Document object
  # if the output is not a well-formed XML, an exception is thrown
  output = Document.new(response.body)
  # and here you can use REXML APIs, including XPath
  # to retrieve pieces of data from the XML

end

Here is one example how REXML::XPath can be used to test template output:


...
require 'rexml/document'
...
  def test_show
    @request.path   = '/test/show'
    @request.action = 'show'
    @request.request_parameters['id'] = '1'

    response = ConversationController.process_test(@request)

    # Parse the response body
    xml = nil
    assert_nothing_thrown { xml = REXML::Document.new(response.body) }
    
    # Retrieve all hyperlinks from somewhere deep inside the response
    title_hrefs = REXML::XPath.match(xml,
        '/html/body/table/tr[2]/td/table/tr[1]/td/a')

    # Check that there are three hyperlinks...
    assert_equal 3, title_hrefs.size

    # ... and they point where they should
    href_urls = title_hrefs.map {|element| element.attribute('href').value }.sort
    assert_equal ['/conversation/show?id=1', '/forum/?id=1',
        '/message/new?conv_id=1'], href_urls
  end

Also, for each action I write at least one test that writes the response body to a file in some temporary directory. I can then use tidy on all saved HTML files to clean out the warnings.

AlexeyVerkhovsky

——

Question 1: what is the “best” way to validate view output against XHTML DTD?

Answer: There is a ruby-tidy gem that you can use to validate XHTML rather than, as suggested above, write it to a file and use the tidy commandline. You’ll need to complile/install the regular C version of tidy. The Tidy gem just loads the C library. See: http://tidy.rubyforge.org/

Question 2: If I wanted to test that the view displayed a particular section if a certain @flash item was set, how do you set up the @flash object in the request object? I tried assigning a hash to request.session‘flash’, but that doesn’t seem to work.

Answer: The request takes four parameters, the final being the flash parameters. for example “get :index, nil, nil, { :what => "ever” }

Comment: I’ve been playing a bit with the “from the view and down” approach to functional testing. Here are my first impressions .

——

Testing a Controller that requires Login

Here’s the scenario:

  • the CategoriesController requires an admin to be logged in before allowing access to any of its actions
  • the UserLoginController is the controller that handles authenticating admins

Question: How do you test the CategoriesController?

Answer:

Step 1

Create a login function called login in test_helper.rb, that automatically logs in a user.

Step 2

In the setup function of CategoriesController, include the following:


    # to deal with the Categories controller we need to login first.
    # We do this by switching the current controller @controller
    # to the UserLoginController, logging in, then switching the
    # current controller to CategoriesController.
    @controller = UserLoginController.new
    login # in test_helper.rb
    @controller = CategoriesController.new

Since setup is called before each test, a user will be logged in before performing any actions in the test.

Step 3 (optional)

To be able to set which user logs in, you can add the following to test_helper.rb. You can then call login_first from any of your tests.


def login_first(username, password)
  controller = @controller
  @controller = UserLoginController.new
  login(username,password) # also in test_helper.rb
  @controller = controller
end

Of course, you can also call login_first in setup à la Step 2 so you don’t need to include it in each test for a given controller.

category:Howto