20: Testing your site (Part 2)




Learning Rails show

Summary: <h2>Goals</h2> <p>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.</p> <h2>Setup</h2> <p>The code in this episode is the same as the ending state of the previous episode and contains all of the fixed tests.</p> <ul> <li><a href="/learningrails_20.zip">Learning Rails example app code as of the end of this lesson</a></li> </ul> <h2>Functional Tests</h2> <p>We continue where we left off, having working unit tests but many broken functional tests.</p> <h3>Fixing the Functional Tests</h3> <p>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:</p> <pre> FEEFFEEFEEFFEEFFFFEFFFFFFFFF.........FEEEE. </pre> <p>Which in my first run was equivalent to 43 tests, 36 assertions, 20 failures, 13 errors. Ouch.</p> <p>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.</p> <p>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 &lt;:success&gt;, but was &lt;302&gt;”. 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 <code>restful_authentication</code> plugin that we used provides a utility module called <code>AuthenticatedTestHelper</code>. 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 <code>before_filter :login_required</code> checks, that will cause any of our tests that try to directly exercise an action to instead get redirected to a login screen.</p> <p>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).</p> <p>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:</p> <pre> class CategoriesController &lt; ActionController::TestCase include AuthenticatedTestHelper fixtures :users </pre> <p>Now, let’s use the new method we have to log in as the user quentin:</p> <pre> def test_should_get_index login_as :quentin get :index assert_response :success assert_not_nil assigns(:categories) end </pre> <p>Run the tests again. The redirect failure on <code>test_should_get_index</code> should now be gone.</p> <p>Go back to each method in this test class and add the <code>login_as :quentin</code> line at the start of each test method. All of your redirect failures should now be cleaned up.</p> <p>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 <code>before_filter</code> (links_controller_test, messages_controller_test, pages_controller_test, users_controller_test).</p> <p>For the controllers that are selective about the login requirement, be sure to <em>not</em> 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.</p> <p>Run the tests again. We are getting closer. In my run, the results look like this:</p> <pre> Started FEE..EEFEE.E.EEF...E.F................FEEEE. Finished in 0.547902 seconds. </pre> <p>Our <code>CategoryControllerTest</code> is responsible for a number of those errors and failures, so let’s clean it up next.</p> <p>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 <code>Category</code> model, we see a validates_presence_of on :title. We better fill in a value for the title:</p> <pre> def test_should_create_category login_as :quentin assert_difference('Category.count') do post :create, :category =&gt; { :title =&gt; 'test' } end assert_redirected_to category_path(assigns(:category)) end </pre> <p>Go through the rest of the code and find and fix the various create tests that need values.</p> <p>Some test methods seem to be using fixture data, note the <code>categories(:one)</code> syntax we see in methods like <code>test_should_show_category</code>:</p> <pre> def test_should_show_category login_as :quentin get :show, :id =&gt; categories(:one).id assert_response :success end </pre> <p> </p> <p>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:</p> <pre> def test_should_show_category login_as :quentin get :show, :id =&gt; categories(:ruby).id assert_response :success end </pre> <p>Go through all of the test code and fix up renamed fixtures now.</p> <h3>Writing a new functional test</h3> <p>There are places where we added methods to our RESTful controllers. For instance, in the <code>LinksController</code> we added the <code>list</code> action. We add a test for it in <code>LinkControllerTest</code>:</p> <pre> def test_should_get_list get :list, :name =&gt; 'pageone' assert_response :success assert_not_nil assigns(:categories) assert_not_nil assigns(:pagetitle) assert_not_nil assigns(:page) end </pre> <p>In this example, we pass a parameter to the <code>list</code> action. <code>list</code> expects the name of the page so it can be passed through to the <code>get_page_metadata</code> helper we wrote, which itself loads up <code>page</code> and <code>pagetitle</code> 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 <code>LinksControllerTest</code> and we tweak the fixture file itself so the with the id <code>one</code> has a <code>name</code> value of “pageone”. (See the source code if this is unclear.)</p> <p>We apply the same fix to the @MessagesControllerTest@’s <code>test_should_get_new</code> 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.</p> <p>Looking at the code for this action, we see that if validation fails when updating attributes, the action simply displays the <code>edit</code> form again, and hence we get an <span class="caps">HTTP</span> 200 code. Peeking inside of the default fixture data for messages, we see the problem. Change the email fields to be properly formatted:</p> <pre> 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 </pre> <h3>Final cleanup</h3> <p>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 <em>not</em> logged in. Let’s write a quick example of a test that succeeds when an unauthenticated person tries to get a protected resource. In <code>LinkControllerTest</code>, add this test:</p> <pre> def test_should_not_get_index_not_logged_in get :index assert_redirected_to new_session_path end </pre> <p>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 <code>assert_redirected_to new_session_path</code> uses the RESTful route to the session controller’s new action. This is where login is initiated.</p> <p>Run the test, and success!</p> <h2>Further Exercises</h2> <ol> <li>Install the ZenTest gem and take autotest for a spin (gem install ZenTest). This will continually run your tests as you code and save files. A real time saver.</li> <li>Install <a href="http://opensource.thinkrelevance.com/wiki/tarantula">tarantula</a> to explore integration testing and data fuzzing</li> <li>See our <a href="http://www.buildingwebapps.com/podcasts/79332-testing-rails-code">Testing</a> topic for many other articles</li> </ol>