Ruby on Rails
HowtoUnitTest

This document has been superseded by A Guide to Testing the Rails

Please don’t delete this, it’s a great basic explanation of testing. Just make sure people know to move on to the Guide :)

Intro

Unit tests allow you to test isolated individual parts of your system to make sure they work. For example:

In Rails, the unit testing generally tests the backend, or models.

The Hierarchy

There’s a hierarchy involved with unit tests. It starts at an individual assertion (more later), and ends at a suite (more later).

Bottom up, it looks like this:

The Ruby manifestations of these are:

What This Means In Rails

In the context of Rails, test suites are pretty much irrelevant. The rakefile takes care of merging your test cases together.

A test case, again, in the context of Rails, is a file that lives under test/unit off your application’s root directory. If you’ve used the new_model script (and I highly recommend it) to create your models, you’ll see that a test unit for each model has been created for you. Cool eh?

A unit test is a scenario you’re trying to prove. Take the above example “can a user change their password?”. This test might look like this:

As mentioned before, tests are functions. They must start with the first 5 letters test_ as in test_for_steroids or test_schmest.

Assertions are logical checks which cause a test to pass or fail. For example:

These assertions along with other code make up a test. In the unit test example above, those lines of code might look like this:

For Those Still Awake; Your Reward Is Code!

Unit tests are one of those things (like juggling) that takes longer to explain than it does to get started. Here’s an example of a unit test for a model called Person. In fact, if you ran the new_model script to create your model, this is exactly what gets generated as test/unit/person_test.rb.

require File.dirname(__FILE__) + '/../test_helper'
require 'person'

class PersonTest < Test::Unit::TestCase
  def setup
    @people = create_fixtures "people"
  end

  def test_something
    assert true, "Test implementation missing"
  end
end

Voila. A working Test Case with 1 Test containing 1 Assertion. Completely useless, but hey, it passes!

Remember our password example? The test (ie. function) might look like this.

def test_passwords
  jimmy = Person.new
  jimmy.first_name = 'Jimmy'
  jimmy.last_name = 'McJimmy'
  jimmy.password = 'h1st0r3ct@my'
  assert !jimmy.password.empty?
  assert jimmy.password.length > 5
  assert_match /(0-9)/, jimmy.password
  assert_not_equal 'password', jimmy.password.downcase
end

Adding this function to our test and rerunning it should also pass.

BTW, to run a test, you can go through the rakefile, or you can change to the test/unit directory and type ruby person_test.rb. The tests are self-sufficient enough to run on their own.

That’s pretty much all there is to getting started. Of course, there’s always best practices when it comes to test units.

For example, IMHO, the test_passwords test is kinda weak. I mean, ya, sure, it passes, but we haven’t tested the other scenarios like:

# make sure we can't validate (assuming the model properly
# validates all the password rules )

  # what happens when we pass a blank password? (eek!)
  jimmy.password = ''
  assert !jimmy.valid?

  # what happens if nil gets passed?  (gasp!)
  jimmy.password = nil
  assert !jimmy.valid?

  # what happens if we pass a number?! (omg!)
  jimmy.password = 69
  assert !jimmy.valid?

  # what happens if we pass 'password' (yikes!)
  jimmy.password = 'password'
  assert !jimmy.valid?

Summary

Anyway, you get the point. Destructive (or negative) testing is arguably more important than positive testing. I mean, the code you write is also correct right??? :)

Well, try making it blow up as many ways as possible and you’ll have robust (completely over-used word) code.

Happy testing!

PS. Don’t be surprised if your unit test LOC count is several times larger than your actually application code. This is the case in my current project in progress where my unit tests outweigh my actual app code by a factor of > 6!

+----------------------+-------+-------+---------+---------+-----+-------+
| Name                 | Lines |   LOC | Classes | Methods | M/C | LOC/M |
+----------------------+-------+-------+---------+---------+-----+-------+
| Helpers              |   604 |   508 |       0 |      53 |   0 |     7 |
| Controllers          |  1130 |   742 |      35 |      85 |   2 |     6 |
| Units                |  7454 |  5746 |      43 |     326 |   7 |    15 |
| Functionals          |  2149 |  1406 |      63 |     190 |   3 |     5 |
| Models               |  1737 |   918 |      43 |      86 |   2 |     8 |
+----------------------+-------+-------+---------+---------+-----+-------+
| Total                | 13074 |  9320 |     184 |     740 |   4 |    10 |
+----------------------+-------+-------+---------+---------+-----+-------+

Cheers…

Useful Tip

what-a-day

—-

Question: how to unit-test a template? I want to find a way to invoke a template with mock inputs and validate the output against verbatim expected output string.

AlexeyVerkhovsky

—-
Do your tests hang when running them with rake, but work fine from the comand line? See: http://weblog.rubyonrails.com/archives/2004/12/30/running-rake-tests-with-ruby-182/
and http://dev.rubyonrails.com/ticket/474

SteveYen

—-
Firstly Sorry for my bad english :). is there any good example to test my own library classes?

Desperate?

category:Howto

—-
Other Resources
There is a useful article on testing exception handling with exception_raise here:
http://www.oreillynet.com/onlamp/blog/2007/07/assert_raise_on_ruby_dont_just.html

Testing transactions in Rails is slightly problematical, but can be simply overcome using
self.use_transactional_fixtures = false
see: http://railspikes.com/2007/3/28/testing-transactions