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.
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.
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.
——
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 .
——
Here’s the scenario:
CategoriesController requires an admin to be logged in before allowing access to any of its actionsUserLoginController is the controller that handles authenticating adminsQuestion: How do you test the CategoriesController?
Answer:
Create a login function called login in test_helper.rb, that automatically logs in a user.
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.
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.
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.
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.
——
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 .
——
Here’s the scenario:
CategoriesController requires an admin to be logged in before allowing access to any of its actionsUserLoginController is the controller that handles authenticating adminsQuestion: How do you test the CategoriesController?
Answer:
Create a login function called login in test_helper.rb, that automatically logs in a user.
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.
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