Lesson 20
Testing your site (Part 2)
Goals
In this lesson, we finish up our testing journey by fixing up some of our functional tests. Please note that, while we’ve tried to make these notes complete, they aren’t the full tutorial; that’s in the screencast, which you can access via the link on the left.
Setup
The code in this episode is the same as the ending state of the previous episode and contains all of the fixed tests.
Functional Tests
We continue where we left off, having working unit tests but many broken functional tests.
Fixing the Functional Tests
We now know how to read the test results, so switching our attention to the functional test block results, we see we have our work cut out for ourselves:
FEEFFEEFEEFFEEFFFFEFFFFFFFFF.........FEEEE.
Which in my first run was equivalent to 43 tests, 36 assertions, 20 failures, 13 errors. Ouch.
The Rails scaffold generator does a much better job at creating functional tests than it does with unit tests. Functional tests exercise our controllers. Peeking inside most of the test files in the test/functionals directory, you will note that they follow similar patterns.
So what went wrong? In scanning through the test diagnostics, we see a lot of tests with similar issues. There are many tests with problems with redirects: “Expected response to be a <:success>, but was <302>”. There are many tests where things were not created, updated, or destroyed when expected. Thinking back, the common thread here is that all of these things need you to be logged in, yet, when the tests are automatically generated for us, the generator has no idea about this requirement. Let’s fix that. The restful_authentication plugin that we used provides a utility module called AuthenticatedTestHelper. This module contains useful methods that you can mix-in to your test classes to simulate things like logging in, precisely what we need. Since we protected most of our administrative functions with those before_filter :login_required checks, that will cause any of our tests that try to directly exercise an action to instead get redirected to a login screen.
Let’s pick one of our controller’s tests to fix up as our example. The others will follow similar patterns and you can look at the final project code to see the changes (or try to make the changes yourself as an exercise).
First, add the authentication system’s helper at the top of the test class. While there, add the user fixtures so we have some test user accounts to log in with:
class CategoriesController < ActionController::TestCase
include AuthenticatedTestHelper
fixtures :users
Now, let’s use the new method we have to log in as the user quentin:
def test_should_get_index
login_as :quentin
get :index
assert_response :success
assert_not_nil assigns(:categories)
end
Run the tests again. The redirect failure on test_should_get_index should now be gone.
Go back to each method in this test class and add the login_as :quentin line at the start of each test method. All of your redirect failures should now be cleaned up.
But, there are still failures to fix. Look through the other functional tests and make the same changes to all of the others that are protected with the before_filter (links_controller_test, messages_controller_test, pages_controller_test, users_controller_test).
For the controllers that are selective about the login requirement, be sure to not login when it isn’t needed. For instance, @MessageControllerTest@’s new and create tests are not protected by login. See the controller to confirm this.
Run the tests again. We are getting closer. In my run, the results look like this:
Started FEE..EEFEE.E.EEF...E.F................FEEEE. Finished in 0.547902 seconds.
Our CategoryControllerTest is responsible for a number of those errors and failures, so let’s clean it up next.
test_should_create_category fails with a pattern that will become familiar to you as you test code that uses validations. Here, the code is trying to make a new category object. The test, however, by default tries to create an empty category. Looking inside of the Category model, we see a validates_presence_of on :title. We better fill in a value for the title:
def test_should_create_category
login_as :quentin
assert_difference('Category.count') do
post :create, :category => { :title => 'test' }
end
assert_redirected_to category_path(assigns(:category))
end
Go through the rest of the code and find and fix the various create tests that need values.
Some test methods seem to be using fixture data, note the categories(:one) syntax we see in methods like test_should_show_category:
def test_should_show_category
login_as :quentin
get :show, :id => categories(:one).id
assert_response :success
end
The generator script preloaded the fixtures with the test data it created when we started. Since we renamed the IDs in our fixture files, we need to tweak all instances of fixture usage to use valid ID symbols:
def test_should_show_category
login_as :quentin
get :show, :id => categories(:ruby).id
assert_response :success
end
Go through all of the test code and fix up renamed fixtures now.
Writing a new functional test
There are places where we added methods to our RESTful controllers. For instance, in the LinksController we added the list action. We add a test for it in LinkControllerTest:
def test_should_get_list
get :list, :name => 'pageone'
assert_response :success
assert_not_nil assigns(:categories)
assert_not_nil assigns(:pagetitle)
assert_not_nil assigns(:page)
end
In this example, we pass a parameter to the list action. list expects the name of the page so it can be passed through to the get_page_metadata helper we wrote, which itself loads up page and pagetitle instance variables to be used to set the page title and appropriate tabs in our navigation interface. To make this work, we have to include the pages fixture to LinksControllerTest and we tweak the fixture file itself so the with the id one has a name value of “pageone”. (See the source code if this is unclear.)
We apply the same fix to the @MessagesControllerTest@’s test_should_get_new method. While we are looking at MessagesControllerTest, we see one last failure on the update action. It is expecting a redirect (proper behavior on an update) but instead is getting a 200 response.
Looking at the code for this action, we see that if validation fails when updating attributes, the action simply displays the edit form again, and hence we get an HTTP 200 code. Peeking inside of the default fixture data for messages, we see the problem. Change the email fields to be properly formatted:
one: name: MyString email: bob@example.com company: MyString phone: MyString subject: MyString body: MyText two: name: MyString email: two@example.com company: MyString phone: MyString subject: MyString body: MyText
Final cleanup
We lost a valuable piece of information when we fixed all of those authentication tests. We really should also test that the right thing happens when someone is not logged in. Let’s write a quick example of a test that succeeds when an unauthenticated person tries to get a protected resource. In LinkControllerTest, add this test:
def test_should_not_get_index_not_logged_in
get :index
assert_redirected_to new_session_path
end
Here, we are not logged in, and we are trying to get the index of links. We should not be able to do this, and we expect to be redirected to the log-in screen. The assert_redirected_to new_session_path uses the RESTful route to the session controller’s new action. This is where login is initiated.
Run the test, and success!

Reader Comments
6 comments
Functional Test Failure: expected a redirect to...found one to ...
From: Wayne Simacek, 09/22/09 07:54 AM
I'm having problems clearing 9 failures and 4 errors in my functional tests. The top Failure: test-should-create-message(MessageControllerTest) expected a redirect to <{"action"=>"show", "id"=>"996332878", "controller"=>"messages"}>, found one to <{"name"=>"home", "action"=>"show", "controller"=>"viewer"}>, a difference of <{"name"=>"home", "id"=>"996332878", "controller"=>"viewer"}> I've compared the messages_controller.rb and the messages_controller_test.rb with the download and I don't see any differences. Any suggestions?
Functional Test Error: undefined method `[]' for #
From: Wayne Simacek, 09/22/09 05:33 AM
I was having some problems cleaning up all my functional test errors and I got the error above. I "Googled it" and found out it comes from an incompatibility between rails 2.0.2 and ruby 1.8.7 as noted here: http://www.mail-archive.com/debian-bugs-dist@lists.debian.org/msg528878.html To fix it you can do one of three things: 1. Upgrade your app to rails 2.1.0 or greater 2. Downgrade your version of ruby to 1.8.6 (not suggested) 3. or place the following code in config/initializers/enurmerable_fix.rb unless '1.9'.respond_to?(:force_encoding) String.class_eval do begin remove_method :chars rescue NameError # OK end end end After re-starting the computer (re-start of server is all that's required, but I wasn't sure about test environment. >>This took care of those type of errors, although my next step after I get all my tests passing is to do the rails upgrade. Regards, Wayne Simacek
RE: XML output
From: Christopher Haupt, 11/02/08 08:19 PM
Hi Marius: To get to the XML output using the default routing that we set up in the application, you can simply end the URL with .xml: @www.railsapp.com/messages.xml@ will trigger the format.xml lines. The render commands that are used have a number of options that are worth checking out in the online doc. They let you constrain which fields of your model get converted into XML, as well as some control over the XML formatting itself. XML can be triggered otherways (such as Accepts type headers), but the above is the most explicit. -c
xml output
From: Marius, 11/01/08 05:19 PM
Hello Christopher! I want to have the view of this rails app as xml (not only in html). I can see this line "format.xml { render :xml => @messages }" in the controllers but I dont know how to use it. Is it possible to create a method which can be called in the url like www.railsapp.com/messages/xml and this could show up a xml version of the listing ?! Thanks for your help
Great work
From: Chris Porter, 07/13/08 06:40 AM
Thanks for these amazing screencasts.
Course syllabus
From: Marcos Ricardo, 07/07/08 06:57 AM
Hi Michael and Christopher,
I would like to know, if possible, how long is the way we are going on here.
How many lessons you have planned to this course ?
I just want to know if we are close to the middle or close to the end of the course.
Thanks in advance.