Monday, 29 September 2008

Rails - Testing

Now that we have a Rails app up and running, all be it a simple one, it's time to think about testing. When you generate an application you also get a test structure created for us. This structure lets us create unit, functional and integration tests. Each kind of test has a different scope and I'll start, as we should, with unit tests.

Looking in the test/unit directory there's a single ruby source file for each model created previously, each of these files looks like this

class BlogEntryTest < ActiveSupport::TestCase
  # Replace this with your real tests.
  def test_truth
    assert true
  end
end

Pretty straightforward, assert that true is true. (If this is your first glance at Ruby code the first line says that BlogEntryTest derives from TestCase and the 'def' statement defines a method).

Before running the test I create the test database

mysqladmin -u root create rblog_test

There are two ways (at least) to run this test, running

ruby -I test test/unit/blog_entry_test.rb 

(-I test here includes the test directory in the search path) or

rake test:units

Running the first command line on my machine gives this:

Loaded suite test/unit/blog_entry_test
Started
E
Finished in 0.033476 seconds.

  1) Error:
test_truth(BlogEntryTest):
ActiveRecord::StatementInvalid: Mysql::Error: Table 'rblog_test.users' doesn't exist: DELETE FROM `users`

This shows us that the database tables don't exist. Running

rake db:test:prepare

fixes this and re-running the test now succeeds.

Started
.
Finished in 0.095619 seconds.

It's also possible to run

rake test:units

This will run all the unit tests

Started
...
Finished in 0.056533 seconds.

3 tests, 3 assertions, 0 failures, 0 errors

Now that there's some confidence that the testing framework is in place it's time to start thinking about real tests.

The user class represents a user of the system, either a user with a blog or a user posting comments. This user must have a username, email and password. The user class looks like this

class User < ActiveRecord::Base
  validates_presence_of :name
  validates_presence_of :email
  validates_uniqueness_of :email
  validates_confirmation_of :password
  validate :password_non_blank

This suggest some tests. Does the user have a name and email, is the emil unique and does the password have some data! The first test checks that the user is valid

class UserTest < ActiveSupport::TestCase
  def test_empty_user_is_invalid
    user = User.new
    assert !user.valid?
    assert !user.errors.empty?
    assert user.errors.invalid?(:name)
    assert user.errors.invalid?(:email)
    assert_equal "Missing password", user.errors.on_base
  end

The test creates a User then calls the valid? method (if you're new to Ruby the ? on the end of a method is part of the method name and indicates that the method returns a boolean, a ! on the end indicates that the method mutates data) . The test asserts that the user is not valid and then asserts that the appropriate errors have been added to the errors collection.

The code has other tests for the user, checking that the password and password_confirmation match and that the password is not blank. There is also a test that a User is valid if all the fields are set correctly, none of these are shown here but the code is available here not that there's much to see at the moment!

The final test for the moment checks that the User must have a unique email. The code looks like this:

def test_user_unique_email
  user = User.new(:name => "Test Name",
                  :email => users(:kevin).email,
                  :password => "wibble",
                  :password_confirmation => "wibble")

  assert !user.save
  assert_equal "has already been taken", user.errors.on(:email)
  assert_equal ActiveRecord::Errors.default_error_messages[:taken], user.errors.on(:email)      
end

The thing of interest in this code is the line

:email => users(:kevin).email,

This loads a fixture named :kevin. Fixtures are test data defined in a yml file (YAML Ain't a Markup Language) file. Fixtures have names and cen be loaded by name in the test code. The fixture looks like this:

kevin:
  email: kevin@test.com
  hashed_password: hash
  name: Kevin

The fixture data is loaded into the database, then the line 'users(:kevin).email' loads the fixture and gets its email value. This means that the test tries to save a user with the same email address as one that already exists, and that should fail.

Posted by kevin at 11:33 AM in Ruby

 

[Trackback URL for this entry]

Your comment:

SCode: (*) Generate another code
SCode

Please enter the code as seen in the image above to post your comment.

(not displayed)
 
 
 

Live Comment Preview:

 
« September »
SunMonTueWedThuFriSat
 123456
78910111213
14151617181920
21222324252627
282930