+44 (0)1179 113711

Rock Solid Knowledge Blogs

An aggregation of all the Rock Solid Knowledge Blogs

Subscribe  Subscribe

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 29/09/2008

Learning Rails - Part 2

I wanted to make this a series about Rails and about Ruby. To that end I'm going to write a series of entries on building an MVC application. As I said in the previous post I am a Ruby and Rails neophyte, so I'll be learning as I go along, regard this as a developer's journal. The aim being that you can learn from the mistakes that I make. If this really gets going I also hope to do some screencasts along the way.

What am I going to write - a blog of course (is there any other sort of web app out there today :) ). I chose blogging software because

  • Everybody understands what a blog is, so no trying to figure out the behaviour
  • It gives me a chance to play with different formats (html and RSS)
  • At some point I'll need to write support for the various APIs (atom, etc)
  • There'll be chances to use AJAX
  • I can learn how to deploy to Apache
  • It shouldn't take too long to get things going

I'm going to rely heavily on the 'Agile Development With Rails' book, and hopefully any feedback I get. I'm learning Ruby at the same time as Rails and Ruby is a very idiomatic language, I know I'll get some of the idioms wrong, so again feedback is welcome. Oh and initially I'm doing this on MacBook using TextMate as the editor. At some point I'm going to play with NetBeans and Eclipse as tools and so try out JRuby, not that I like a challenge of learning 17 new things at once!

Start at the beginning. Go on the end and then stop, said the Red Queen

So the beginning of a Rails app is the code generator, to create the application I fire up a terminal make sure I'm in the right directory and call

rails rblog

This gives the well known directory structure of

app
config
db
doc
lib
log
public
script
test
tmp
vendor

Most of the time I'll be in the app directory (that's where the models, controllers and views are), but I'll also use stuff from the db directory where the 'migrations' are stored, in the public directory where public the web files are and in config, where various setup files live.

Once the application has been created I can fire up another terminal (I usually have three open, one for my commands, one for the server and another to see the log file), change the the project directory and run the server with the command 'script/server'

This starts the server on port 3000, the server I'm using here is Mongrel, you can also start WEBrick. There's plenty of discussion on these servers out on the web so I won't go into the differences here.

Once the server is running you can point your browser at http://localhost:3000 and you should see something like:

RailsHomePage

I'm going to use MySql for the database and this needs to be configured. To do this I've edited config/database.yml to look like this:

development:
adapter: mysql
encoding: utf8
database: rblog_development
username: root
password:
socket: /tmp/mysql.sock

Adding similar entries for test and production databases. Once that's done I created the development database

mysqladmin -u root create rblog_development

Now that all the necessary structure is in place I can create the first controllers and models. What do I know about the blog? I know it's going to have users, users can have blogs and blogs will have entries and comments. There may be more things eventually, such as tags, categories, pingbacks etc, but for now that's enough. So not to get too far ahead of myself I decided to create scaffolding for users, blogs and blogentries.

For the users model I'm going to user a similar approach to that used in the Rails book, so the user will have an email address and password, the password will be stored as a hash will be salted. A user will have a name that can be displayed on comments or on a blog and users may also own blogs. For now I'm going to limit this to one blog per user, but in the future this may expand to multiple blogs.

Running

script/generate scaffold user email:string hashed_password:string name:string blog_id:integer

gives me what I want. Doing something similar for blogs and blogentries

script/generate scaffold blog title:string sub_title:string owner_id:integer admin:boolean
script/generate scaffold blog_entry title:string entry:text author_id:integer entry_added:datetime entry_last_edited:datetime

creates those models and their corresponding controllers and migrations.

Notice that as in my previous post I'm using singular names for the model components, so user, blog and blog_entry.

Now that's done it's then onto setting out some of the UI and writing some unit testing code.

Posted 25/09/2008

Learning Rails - Part 1

I've been looking at Ruby and Rails for a long time, when I say looking at here what I really mean is staring through the glass and wondering what the fuss was about. Now that I finally have a MacBook I decided to actually sit down and learn Rails and Ruby at the same time. This means that on my hour long commute to and from my current contract I'm writing web code, and much fun it is. The commute is split over two trains so each part is about 30 minutes which gives me a very fractured learning experience but I'm still getting something done.

I wanted to use the web app I'm working on (it's only a toy) as a learning tool. This means that a) I wanted to minimize the new concepts and b) not be led by the hand too much. Because of that I decided that I would initially not use REST based routing as I would need to understand more about REST and using the built in routing would hide the routing system from me. So taking my routes in hand I initially did things the 'hard' way (actually, it's not that hard)

I finally figured that I knew enough to then try the RESTian approach, so firing up my trusty console, and TextMate I tried adding a new controller and model to the application.

I ran a command, something like,

script/generate scaffold foo

then ran

rake db:migrate

pointed the browser at http://localhost:3000/foos and everything seemed good. I added a new foo, life was good. Then I tried to browse to http://localhost:3000/foo/1 and I get an error:

Unknown action
No action responded to 1

After much Googling and running of

rake routes

I couldn't find an answer.

Eventually after much re-reading of the routing chapter in The Book something must have lodged in my head and I decided to kill the web server and restart it, and suddenly the routes started working. The moral is "you do sometimes need to re-start the rails web server"!

The second issue was even more stupid. After fix the above problem, and getting my head more around the REST idea I decided I needed a bunch of controllers, so I duly run

script/generate scaffold bars

restart the server and immediately get strange errors along the lines of

undefined method `edit_bars_result_path' for #<ActionView::Base:0x255a508>

eh!

Rails experts will be laughing right now, or thinking 'been there, done that.' Rails adopts naming conventions, in this case models and controllers are singular. RESTian routing is based around the ideas of things and collections of things, the things are singular and the collections are plural. I tried to generated things that were already plural (bars), and the RAILs pluralization had no idea what to do with it, so the naming broke (at least that's what I think happened :) )

Posted 22/09/2008