8.9. Testing Controllers

Testing is an important component of performing Agile Web development. In fact, as stressed multiple times before, one of the fundamental principles of the Agile methodologies is the ability to change the software code base to respond to changes in requirements.

It's easy enough to verify that routing and controllers are working as expected by firing up your favorite browser and trying out paths within the application. But this approach is not systematic and won't provide you with any level of confidence, as you continue to change and evolve your application. Furthermore, working on a code base that doesn't have good test coverage is risky and tends to require much more time and effort while debugging.

As a reminder, at any time you can check your test coverage and other interesting statistics about your project by running rake stats.

For these reasons, this chapter concludes with a brief tour of what Rails bakes-in for testing routes and controllers.

8.9.1. Testing Routes

Route testing consists of writing unit tests that verify that the mapping between paths and the controllers' code works as expected.

Your routing tests can be stored in testunit outing_test.rb, which is just going to be a regular test case (within the context of Rails):

require 'test_helper'

class RoutingTest < ActiveSupport::TestCase

 #... Your routing tests...

end

This file is not generated automatically by Rails and must be manually created by developers who intend to test routes.

Routing consists of two parts: generating paths from amidst a bunch of options (like :controller and :action), and obtaining these same options by recognizing paths. Not surprisingly then, Rails provides us with three assert methods related to routes: assert_generates, assert_recognizes, and assert_routing, which unites the two previous ones.

These are defined in ActionController::Assertions::RoutingAssertions and their signatures are as follow:

assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)
assert_recognizes(expected_options, path, extras={}, message=nil)
assert_routing(path, options, defaults={}, extras={}, message=nil)

extras is used to store query string parameters. extras and options need to use symbols for keys and strings for their values, or assertions will fail. Ignore the defaults parameter that's currently unused. As usual, the message parameter is used to provide a custom error message upon failure.

If you consider the blog application, you could use assert_generates to write the following assertion:

assert_generates("/articles/unpublished", :controller => "articles",
                                           :action => "unpublished")

This verifies the assertion that the hash of options { :controller => "articles", :action => "unpublished" } generates the path /articles/unpublished.

The opposite of that assertion would be:

assert_recognizes({ :controller => "articles", :action => "unpublished" },
                  "/articles/unpublished")

This verifies that the path /articles/unpublished is correctly recognized in the hash of options { :controller => "articles", :action => "unpublished" }.

Finally, both of them can be tested at the same time through assert_routing. For example, in the following very simple test case, I tested for both recognition and generation of the path:

require 'test_helper'

class RoutingTest < ActiveSupport::TestCase

load "config/routes.rb"

 test "generating and recognizing /articles/unpublished" do
  assert_routing("/articles/unpublished", :controller => "articles",
                                          :action => "unpublished")
 end
end

Notice that the routes.rb file needs to be loaded explicitly.

By the way, the descriptive string for test cases is a feature that has been long available through third-party code (typically in BDD frameworks like RSpec and Shoulda) but that has now been integrated into the core. The old syntax (for example, def test_unpublished) is still supported for backwards compatibility.

You can run routing tests by executing rake test:units. If your source code is stored in an SVN or, starting with Rails 2.2, in a Git repository, you can use the command rake test:uncommitted to speed up testing, by limiting the test run to the tests whose files have not been committed yet (for example, they have been changed locally).

Using autotest -rails

Many professional Rails developers like to use autotest, which continuously monitors the files you change so that the associated tests are run. The advantage of this is that you don't have to sit there and wait for your entire test suite to run, because they'll run continuously and will alert you when any test fails or goes into error. This promotes Test-Driven development, reduces the context switch between writing code, using the command line to run tests, and opening up the browser. You can install it with gem install ZenTest (prepend sudo if you are not running Windows) and then run it as autotest -rails in your Rails project. You can read an enthusiastic blog post about it and watch a quick screencast online at http://nubyonrails.com/articles/autotest-rails.


8.9.2. Functional Testing

Functional testing is the process of testing actions within a single controller. If you inspect the testfunctional folder for the blog application, you'll notice that the scaffold generator has created articles_controller_test.rb and comments_controller_test.rb for you. In fact, anytime a controller gets generated a functional test case for it is created as well. When you generate a controller through the controller generator, the functional test case is just a stub; however, scaffold does one better than that. In fact, the following is the automatically generated code for articles_controller_test.rb:

require 'test_helper'

class ArticlesControllerTest < ActionController::TestCase
  test "should get index" do
    get :index
    assert_response :success

assert_not_nil assigns(:articles)
  end

  test "should get new" do
    get :new
    assert_response :success
  end

  test "should create article" do
    assert_difference('Article.count') do
      post :create, :article => { }

   end

    assert_redirected_to article_path(assigns(:article))
  end

  test "should show article" do
    get :show, :id => articles(:one).id
    assert_response :success
  end

  test "should get edit" do
    get :edit, :id => articles(:one).id
    assert_response :success
  end

  test "should update article" do
    put :update, :id => articles(:one).id, :article => { }
    assert_redirected_to article_path(assigns(:article))
  end

  test "should destroy article" do
    assert_difference('Article.count', −1) do
      delete :destroy, :id => articles(:one).id
   end

    assert_redirected_to articles_path
  end
end

Spend a few minutes analyzing this code, and you'll notice that it's quite readable and easy to understand. Consider the first test:

test "should get index" do
  get :index
  assert_response :success
  assert_not_nil assigns(:articles)
end

get :index simulates an HTTP GET request for the index action. This method, and others in "its family," expects an action name and three optional hashes for storing parameters, session variables, and flash messages that should be associated with the request.

Remember that if you are passing a hash to the method, skipping any previous hashes in the signature, you'll need to provide an empty hash to indicate to Ruby that your hash is an nth parameter and not the second one (for example, get :index, {}, { :my_key => "my value" }).

You then expect the response to be successful (200 OK), so you assert that with assert_response :success. Finally assert_not_nil assigns(:articles) asserts that an @articles instance variable was set by the index action.

Let's consider a slightly more complex test, among the auto-generated ones:

test "should destroy article" do
  assert_difference('Article.count', −1) do
    delete :destroy, :id => articles(:one).id
 end

  assert_redirected_to articles_path
end

assert_difference takes two arguments, and indicates that you expect Article.count to be smaller by one, after the code within its associated block has been executed. In fact, within its block here I destroyed an article by simulating an HTTP DELETE request for the destroy action, and passing it an :id parameter. articles(:one).id is just the id of the first of the two records within your fixture.

These two records are automatically loaded in the test database from the default fixture file (articles.yml) in testfixtures.

The test then goes on asserting that after an article has been deleted, it should be redirected to articles_path (once again, these helpers make your life easier).

You can run this test case by executing rake test:functionals, but it's also possible to run a single test case as follows:

ruby -I test test/functional/articles_controller_test.rb

For improved reporting of test errors and failures, you may want to consider the TURN library, available at http://codeforpeople.rubyforge.org/turn.

Scaffolding has also generated a similar test case, which you can always augment, for the comments controller. If you run rake test:functionals you'll notice though that things don't quite work out of the box. And the reason for this is that you changed the default behavior of these controllers by nesting them, and defined your own logic for redirecting so that the blog appears to be laid out logically (well most of it, at least) to its user and visitors. You also changed the fixtures within Chapter 7, as I introduced the subject of testing models, so the current functional test, for example, expects a "one" fixture that doesn't exist.

Consider this as the perfect opportunity to experiment with working with tests. It would be a very beneficial exercise if you were to attempt to correct the default test cases so that they work for your application. You could also add more tests and assertions, and perhaps even change the code of the application, as you discover odd or unwanted behaviors. The keyword here is experiment. Try things out to get acquainted with these concepts; even if you make mistakes, rake test:functionals will tell you when you get it right.

NOTE

It is recommended that you read the functional tests section of the official Rails guide dedicated to the subject of testing, which you can find online at http://guides.rubyonrails.org/testing_rails_applications.html.

It's important to notice that the request type methods available are get, post, put, head, and delete to be able to properly test RESTful controllers. Also, assigns is not the only hash available after one of those five methods has been used. In fact, while assigns stores all the instance variables that were set in the action (as they are accessible to the view), cookies gives you access to the cookies that were set, as flash does to the flash object and session to the session variables and data.

For historical reasons, assigns is the only object whose keys are accessed through round brackets. cookies, flash, and session all use the regular square brackets with a symbol or a string as a key.

After having simulated a request, you also get access to three useful instance variables: @controller, @request, and @response. As their names suggest, these are, respectively, the controller processing your request, the request object, and the response object produced by the controller.

Besides assert_response and assert_redirected_to many more useful assertions are available. Check the API documentation for the module ActionController::Assertions. All the assertions defined by ActionController are contained within six classes of that module: DomAssertions, ModelAssertions, ResponseAssertions, RoutingAssertions, SelectorAssertions, and TagAssertions.

Integration Testing

Whereas functional testing pertains to the domain of a single controller, integration testing aims to test the integration of multiple controllers. This isn't very different from testing a single controller, except that there are a few practical implications. The test case needs to inherit from ActionController::IntegrationTest, its file is not automatically generated for you when a controller is created (use the integration_test generator instead for this), and it needs to be stored in testintegration. Fixtures are not automatically loaded, but you need to explicitly include them through the fixture method. It also means that a few helpers are provided in support of being able to test the flow of the application. For example, there are methods like https! (to emulate a request over HTTPS) and follow_redirect!, open_session, or put_via_redirect, which creates an HTTP PUT request and follows any redirect caused by a controller's response. If you generated the Rails guide in your project, open the file docguides esting_rails_applications.html#_integration_testing in your favorite browser to learn more about integration testing and see a few examples of it.


..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.139.224