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 at 11:33 AM in Ruby

