Introducing Test-Driven Development with Rails 3

Tweet

Following on from my previous posts–Building Your First Rails Application: Models and Views and Controllers–I’m going to cover a simple test-driven approach to adding a new feature to our URL shortener application, Shorty.

To test out this process, we’re going to make the site function more like real life URL shorteners – that is, we’ll test and implement a way to generate a simple short code that represents the URL we’re shortening.

The functionality itself is relatively simple (In our case, we’ll convert the id back and forth between an alpha numeric representation) but it’s a good opportunity to cover a test-driven approach to implementation. To do this, we’ll be using Rails’s integrated testing tools and at the end I’ll also link to a few more options to explore in your own time.

Today, we’ll cover the model portion and in the next post we’ll integrate our shortened urls and write some controller tests.

What We Need

For this to work, we’re going to have to implement three separate things:

  • A way to convert a given stored URL to a short code
  • A way to convert from a short code to a stored URL
  • A new, shorter route to fetch a URL from that.

Getting started, we’ll want to open up the existing test files. When we generated our URL model in the part one, Rails also generated a stubbed out test for us in test/unit/url_test.rb. Opening it up and taking a look, you should see something similar to:

require 'test_helper'

class UrlTest < ActiveSupport::TestCase
  # Replace this with your real tests.
  test "the truth" do
    assert true
  end
end

This is the general structure of a unit test in Rails 3 – the test method lets us declare a method (we also have setup and teardown to deal with maintaining a generalised environment for our tests) and we use asserts (e.g. the assert method call in the code above). Rails (and Test::Unit, the testing framework rails integrate) ship with several assertions out of the box that we can use – for a list, see the methods starting with assert_ at the rails api docs and this older cheatsheet for some of the standard test unit assertions.

Writing Tests

Next, we’ll add some test stubs—empty tests that we can fill out later. To do this, we need to work out exactly what we want to test in the most basic terms. Inside the URL test class, replace the existing test lines with the following:

test 'creating a url lets us fetch a short code'
test 'existing urls have short codes'
test 'converting a short code to an id'
test 'finding a url from a known short code'
test 'finding a url from a invalid short code raises an exception'

Next, from the command line, we can run these empty tests to verify they fail by running the following from our application directory:

rake test:units

Since we haven’t done anything other than write their names, we should get four failures.

Now, we’ll go through our tests 1 by and write them. To get started, fill them out one by one, replacing the test stub as you go:

test 'creating a url lets us fetch a short code' do
  my_url = Url.create(:url => 'http://google.com/')
  # The url should have a short code
  assert_present my_url.short_code
end

test 'existing urls have short codes' do
  my_url = Url.create(:url => 'http://google.com/')
  # Force a fetch from the datbase
  found_url = Url.find(my_url.id)
  assert_present found_url.short_code
  assert_equal my_url.short_code, found_url.short_code
end

test 'finding a url from a known short code' do
  my_url = Url.create(:url => 'http://google.com/')
  assert_equal my_url, Url.find_using_short_code!(my_url.short_code)
end

test 'finding a url from a invalid short code raises an exception' do
  assert_raises ActiveRecord::RecordNotFound do
    Url.find_using_short_code! 'non-existant-short-code'
  end
end

In each of the tests, we test some facet of the expected model behaviour:

  • In our first test we check that once we create a URL, that it has a short code by calling the short_code method and invoking assert_present with its value.
  • In the second test, we create a URL, force-reload it from the database (to simulate fetching it at a later point in time) and then check that it also has a short code and more importantly that the found object has the same short code.
  • In the third test, we create a URL, and test that when we fetch it from the database (using our currently non-existent method find_using_short_code!) that it’ll return the same url.
  • In our last test, we check that when we give it an invalid short code, it raises an exception as expected.

Switching back to the command line and running our tests again using rake test:units, we should still see 4 failures. This is good—it means we have tests but haven’t actually implemented them yet.

Making Our Tests Pass

Now, we’re going to make our tests pass. In order to do this, we need to implement two methods. First, we the short_code instance method on the Url class and then find_using_short_code! class method.

In url.rb, we’ll add a method to generate a short code. For the moment, we’ll just use the base 36 value of id (e.g. 10 will be a):

class Url < ActiveRecord::Base
  validates :url, :presence => true

  def short_code
    id.to_s 36
  end

end

Re-running our tests, we’ll see 2 out of 4 of our tests now pass. Next, we’ll implement a method to find it from the id by doing the reverse conversion (taking a number from the base 36 value):

class Url < ActiveRecord::Base
  validates :url, :presence => true

  def short_code
    id.to_s 36
  end

  def self.find_using_short_code!(short_code)
    find short_code.to_i(36)
  end

end

Running our tests one last time, we’ll see that they all now pass – we can now get and generate the short codes.

Next Steps

In the next post, we’ll cover how to integrate our short codes into the controller and how to test it.

For the moment, see if you can write a few more tests your self – One case worth considering is what happens when you don’t have an id on the model? (e.g. it hasn’t been saved yet).

Eager readers may also want to read up about how to integrate RSpec into their application for an alternative syntax and approach for writing tests. For extra credit, you may also change how we convert back and forth between ids and short codes – e.g. instead of using base 36 (0 to 9 and a to b) you may include the difference between lower and uppercase letters as well.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://codelord.net Aviv Ben-Yosef

    Hey Darcy,
    I really appreciate the initiative as someone that’s trying to learn rails at the moment.

    Just nit-picking about something – TDD, as I know it, talks about writing a test and making it pass. The act of writing lots of tests up front and then making them pass is usually referred to as Test First Development. Why did you choose going that route?

  • http://dakine.co.nz Mark

    In the “Writing our tests” section, you list 5 lines of test declarations, yet from there on forward only talk about 4 tests, there are a few spelling typo’s… a tad disappointing from a sitepoint website

  • Max

    A useful introduction to Test::Unit, especially given the recent discussions about Test::Unit vs. RSpec, etc.

  • http://alivefrommaryhill.net stephen

    Nice series you’re doing here. Thanks

  • http://thebrainpoint.wordpress.com Piyush R Gupta

    Nice article .