Chapter 6: Testing

Rails unit tests let us check our model validations and make sure that the database is working. Functional tests let us simulate a browser hitting the controller actions and verify responses, redirects, variable assignments, and HTML tags. Finally, integration tests let us see how different parts of the application interact by simulating a browser hopping from page to page. In this chapter we will focus on unit and functional tests.

By taking time now to write tests for our models, views, and controllers, we effectively stop moving forward with our application and instead take a step sideways. However this is not a waste of time. Well planned testing strategy can save lots and lots of time in further development stages, and Rails testing infrastructure makes it easy to plan testing very efficiency.

  1. Preparations

    We will use the rake utility to do all the preparations necessary to start the testing process. Open your Ruby Console Window and type:

    rake db:test:prepare

    If you just follow this tutorial, most probably that's all the preparation you need and you can now skip to section 2. In case you changed your default database (sqlite) to any other, read the paragraph below.

    Rails requires a database to run its tests, even if the tests don't need a database. Since testing can potentially destroy or alter data, we'll need to create a testing environment that is nondestructive to data in either the development database or the production database. For this purpose, Rails uses a dedicated testing database, which in our case will be called fake_book_test by convension. Check your config/database.yml file and make sure the test database has the correct name.

    If you switched to MySQL or any other database, you most probably have already created all databases including the test one by calling rake db:create:all. If you chose another method, or you are not sure, you can safely rake your test database now typing:

    rake db:create test
  2. Trivial functional test (Site controller)

    Testing the controller component of a Rails application is called functional testing.

    We will work a lot in the test directory today, which is the default place where Rails and its generators store the testing code. In the functional subdirectory we can find the functional tests for both of our controllers created so far: Site and User.

    Let's start with the Site functional test and have a look at the code:

    test/functional/site_controller_test.rb
    require File.dirname(__FILE__) + '/../test_helper'
    
    class SiteControllerTest < ActionController::TestCase
      # Replace this with your real tests.
      def test_truth
        assert true
      end
    end
    
    

    Since the test file is simply a Ruby file, we can run it from the command line as follows:

    ruby test/functional/site_controller_test.rb Loaded suite test/functional/site_controller_test Started ... Finished in 0.0529 seconds. 1 tests, 1 assertions, 0 failures, 0 errors

    It passed. This is a trivial test, automatically created when we called ruby script/generate controller, and it actually tests nothing at this stage. If this trivial test failed, it's more than likely your database was not properly created or you have incorrect connection information in the database.yml

  3. Functional test of GET requests (Site controller)

    As you should remember from previous chapters, when a new page is required to be display, what the HTTP protocol is doing is called a GET request. What the Site is actually doing is setting three GET requests: for index, about and help.

    We will now replace the test test_truth with three separate tests for all the pages (actions) requested. All the three tests will be very similar. Let's name them test_index_page, test_about_page and test_help_page.

    Let's create the first test for the index page:

    test/functional/site_controller_test.rb
    require File.dirname(__FILE__) + '/../test_helper'
    
    class SiteControllerTest < ActionController::TestCase
    
      def test_index_page
        get :index
        assert_equal "Welcome to FakeBook", assigns(:title)
        assert_response :success
        assert_template "index"
      end
    end
    
    

    Let's review what we've done and what we've tested:

    get :index
    We first simulate the user hitting the page with a GET request using the Rails get function.
    assert_equal "Welcome to FakeBook", assigns(:title)
    The first test verifies that the action defines the @title instance variable and fills it with the correct value.
    assert_response :success
    This checks for the proper HTTP response for success.
    assert_template "index"
    This makes sure that the action renders the proper html templates.
    Exercise 5.1

    Using the test_index_page as a template, write tests for about and help pages, in the same file.

    Running the full test now should produce something like this:

    ruby test/functional/site_controller_test.rb Loaded suite test/functional/site_controller_test Started ... Finished in 0.313 seconds. 3 tests, 9 assertions, 0 failures, 0 errors

    At this point, any failures or errors indicate that you did something wrong in your tests rather that something wrong happened to the code. Check everything! A common mistake is misspelling the title

  4. Functional test of GET requests (User controller)

    The code created in the previous section illustrates some basic registration tests. We now need to do very similar stuff for the User controller...

    Exercise 5.2

    Simply using the same scheme as in Exercise 5.1, create the tests for GET requests for the UserController, index, register, login and logout actions.

    Instead of calling the testing script directly, we can rake all suit of tests:

    rake test:functionals

    Omitting some not very useful information, the important output for this is as follows:

    Started ....... Finished in 0.844 seconds. 7 tests, 21 assertions, 0 failures, 0 errors

    So far, all the test were rather basic. The register action does however some more complex stuff and using the assert_tag function we can probe the actual tags on the page to make sure that they have the proper form. Let's take a look at some of the HTML generated by the registration form to figure out how to test it. Run the application, navigate to the user/register page, click right button of your mouse and choose View source to have a look at the HTML code produced. This should be similar to this:

    This is dynamically generated HTML code. Headers and footers omitted.
    <form action="/user/register" method="post">
    <div style="margin:0;padding:0">
    <input name="authenticity_token" type="hidden" 
    value="18175fb574772b11c84429a440ee6620495dbfbf" />
    </div>
      <p>
        <label for="user_screen_name">Screen name</label>:
        <input id="user_screen_name" name="user[screen_name]" size="30" type="text" />
      </p>
      <p>
        <label for="user_e_mail">E-Mail</label>:
        <input id="user_e_mail" name="user[e_mail]" size="30" type="text" />
      </p>
      <p>
        <label for="user_password">Password</label>:
        <input id="user_password" name="user[password]" size="30" type="password" />
      </p>
      <p>
        <label for="user_password">Password confirmation</label>:
        <input id="user_password_confirmation" name="user[password_confirmation]" 
    	      size="30" type="password" />
      </p>
      <p>
        <input id="user_submit" name="commit" type="submit" value="Register" />
      </p>
    </form>
    
    

    Based on the HTML source code above, let's use assert_tag to test for the presence and correctness of these attributes:

    test/functional/user_controller_test.rb - test_register_page
    def test_register_page
      get :register
      assert_equal "Register", assigns(:title)
      assert_response :success
      assert_template "register"
    
      assert_tag "form", :attributes => {:action => "/user/register", :method => "post" }
      assert_tag "input", :attributes => {:id => "user_screen_name", :size => "30", :type => "text"}
      assert_tag "input", :attributes => {:id => "user_e_mail", :size => "30", :type => "text"}
      assert_tag "input", :attributes => {:id => "user_password", :size => "30", :type => "password"}
    end
    
    

    Now let's run the test again. You can continue to next section if it passes.

  5. Functional test of User POST

    First, we will test a successful registration:

    test/functional/user_controller_test.rb - test_registration_success
    # Test a valid registration
    def test_registration_success
    
      post :register, :user => {:screen_name => "valid_name",
                                :e_mail => "valid@test.com",
                                :password => "password",
                                :password_confirmation => "password"}
    
      # Test assignment of user
      user = assigns(:user)
      assert_not_nil user
    
      # Test new user in database
      # The User found in the database must be equal to one assigned above...
      new_user = User.find_by_e_mail_and_password(user.e_mail, user.password)
      assert_equal new_user, user
    
      # Test if correct notice generated for the flash
      assert_equal "User with login #{new_user.screen_name} created!", flash[:notice]
      
      # Test the redirection
      assert_redirected_to :action => :index
      
      # Make sure user is logged in properly.
      assert_not_nil session[:user_id]
      assert_equal user.id, session[:user_id]
    end
    
    

    Notice that at first, we simulate now the POST request, when user hit the Submit button. Under the name :user (which is the identifier of the web form) we pass all the stuff the user supposedly typed into the form.

    The tests that follow are described in the comment. Analyze the code and once you've finished run the test again to see if the test still passes.

    Now that we've created a test for successful registration, let's make a test for some of the things that can go wrong. We'll post the attributes of an invalid user with 5 digits password (minimum 6 digits) which doesn't match password confirmation.

    # Test an invalid registration
    def test_registration_failed
    
      post :register, :user => {:screen_name => "this_name_is_definitely_too_long",
                                :password => "short",
                                :password_confirmation => "not_the_same"}
    
      assert_response :success
      assert_template "register"
    
      # Test display of error messages.
      assert_tag "div", :attributes => {:id => "errorExplanation", :class => "errorExplanation"}
    
      # Assert that each form field has at least one error displayed.
      assert_tag "li", :content => /Screen name is too long/
      assert_tag "li", :content => /E mail can't be blank/
      assert_tag "li", :content => /Password is too short/
      assert_tag "li", :content => /Password doesn't match confirmation/
    end
    
    

    To fully understand this code, you should navigate to the registration page in the application, fill-in the invalid data, submit and then analyze the generated HTML.

    Now run all three tests and hopefully they all pass.

    Exercise 5.3

    Write another test (eg. test_registration_failed_2) to check against the following error condition:

    • screen name is too short,
    • e-mail is too short,
    • password is too long.
    Exercise 5.4 (difficult)

    Write functional test of the Login page.

  6. Unit test (User model)

    Testing of the models is called unit testing and is quite different from functional testing. Let's take the User model as a testing object!

    The generate script created a sample test and a YAML file at the same time that it created our model.

    A test file located in test/unit reminds the initial skeleton that we've got for the functional testers:

    test/unit/user_test.rb
    require File.dirname(__FILE__) + '/../test_helper'
    
    class UserTest < ActiveSupport::TestCase
      # Replace this with your real tests.
      def test_truth
        assert true
      end
    end
    

    The other file may be found in test/fixtures:

    test/fixtures/users.yml
    one:
      screen_name: MyString
      e_mail: MyString
      password: MyString
    
    two:
      screen_name: MyString
      e_mail: MyString
      password: MyString
    
    

    You may be surprised finding out the latter code in not Ruby. YAML language is indeed not Ruby and not HTML (and not even XML) - but it's very simple and widely used in Rails.

    The tester in user_test.rb reads the YAML and creates two User objects with ids 1 and 2. Within the context of the User test, these objects can be accessed by users(:one) and users(:two).

    Before editing the fixtures, let's first run the test and see if if we can get the trivial test to pass:

    rake test:units
  7. Simple unit test of User validation

    Let's edit users.yml so that the fixtures are more interesting.

    test/fixtures/users.yml
    valid_user:
      screen_name: valid_name
      e_mail: valid@test.com
      password: password
    
    invalid_user:
      screen_name: this_name_is_definitely_too_long
      e_mail: 
      password: short
      
    not_unique_user:
      screen_name: valid_name
      e_mail: another@test.com
      password: justright
    

    We attempt to validate the two users, asserting that the valid user actually is valid and the invalid one not so much. Having those tests pass means that we've configured the database correctly, successfully loaded the fixture, extracted the users by passing the appropriate symbol to the users function created by the fixture, and successfully executed the validation code for each user. We also check that each one of the invalid user's bad traits is invalid by looping over them.

    test/unit/user_test.rb
    require File.dirname(__FILE__) + '/../test_helper'
    
    class UserTest < ActiveSupport::TestCase
    
      # This user should be valid by construction.
      def test_user_validity
        assert users(:valid_user).valid?
      end
    
      # This user should be invalid by construction.
      def test_user_invalidity
        user = users(:invalid_user)
        assert !user.valid?
        assert user.errors.invalid?(:screen_name)
        assert user.errors.invalid?(:e_mail)
        assert user.errors.invalid?(:password)
      end
    
    end
    
    
  8. More advanced unit test of User validation

    We'll start with the uniqueness validation test in the User model:

    validates_uniqueness_of :email, :case_sensitive => false

    test/unit/user_test.rb - test_uniqueness_of_screen_name 
    def test_uniqueness_of_screen_name
      user = User.new(
        :screen_name => users(:valid_user).screen_name,
        :e_mail => "other@test.com",
        :password => "justright")
    
      assert !user.valid?
      assert user.errors.invalid?(:screen_name)
      assert_equal "has already been taken", user.errors.on(:screen_name)
    end
    
    
    Exercise 5.5

    Add a test called test_email_length in user_test.rb to test the validation:

    validates_length_of :email, :within => 6..50 (or whatever you have in your code).

Valid XHTML 1.0 Strict Valid CSS!