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.
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:prepareIf 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 testTrivial 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 endSince 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 errorsIt 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
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 endLet'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.1Using 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 errorsAt 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
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.2Simply 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:functionalsOmitting 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 errorsSo 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"} endNow let's run the test again. You can continue to next section if it passes.
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] endNotice 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/ endTo 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.3Write 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.
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 endThe 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: MyStringYou 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:unitsSimple 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: justrightWe 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 endMore 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) endExercise 5.5Add 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).
Copyright (C) 2009-2011 by Jaroslaw Francik

