Five tips for testing Rails

Posted on May 04, 2008

Tip One – Debugging tests

I use RDebug in my apps a lot, but frequently I found myself wanting to debug my tests. Here’s how you can do it:

If you haven’t done so already, install RDebug
  $ gem install ruby-debug
Insert debugger in your code where you’d like to breakpoint
    def like_bananas?
      # Want to breakpoint here and see what the self.fruit attribute looks like
      debugger
      return self.fruit.likes.include?('bananas')
    end      
And then run the test from the command line using rdebug like this:
    rdebug test/unit/bananas_test.rb

You could even put the debugger reference in your test code as well.

Tip Two – Gently ease yourself into BDD with shoulda

Everybody’s talking about Behaviour Driven Development these days – and the popular BDD tool for Rails is the mighty RSpec.

But, it can seem like quite a bit of a jump to get into RSpec. Maybe you’ve got a well established application with a plethora of Test::Unit tests that you can’t bear to look at. Or maybe you just wanna try out the whole BDD approach for a while and see how it goes without really committing.

Well never fear – there’s a really easy way of easing yourself into BDD and it’s called shoulda. This is a great BDD tool made by the crazy freakin’ geniuses (genii?) over at thoughtbot.

Basically, shoulda is a BDD framework built on top of Test::Unit. It uses all the Test::Unit assertions that we know and love – adds some exotic delicous syntactic BDD sugar – and throws some wonderful helper macros in as freebies..

The thoughtbot folks rabbit on about it over here: http://thoughtbot.com/projects/shoulda.

An example test unit class might look like this:
class FruitBowlTests < Test::Unit::TestCase

  def setup
    @fruit_bowl = get_fruit_bowl
  end

  # Check out this BDD coolness
  should "be best before date" do
    assert @fruit_bowl.fresh?
  end

  # But you can still include old skool tests like this
  def test_fruit_bowl_has_three_fruit
    assert_equal 3, @fruit_bowl.fruit.size
  end

end

So you can try out BDD without messing up your current tests – hooray! Shoulda actually provides way more than this though – but don’t take my word for it, gem install shoulda today!

PS: Not only that, but the shoulda guys include a script that modifies your Test::Unit tests and changes them into shoulda style tests. Run it like this:
 $ ./vendor/plugins/shoulda/bin/convert_to_should_syntax [path to test file]

Tip Three – DRY up your tests with define method

Ever find yourself writing the same tests over and over again but with slightly different modifications – like this:
  def test_banana_should_be_nil_if_fruit_bowl_rotten
    @fruit_bowl = FruitBowl.new(:rotten => true)
    assert_nil @fruit_bowl.banana
  end

  def test_apple_should_be_nil_if_fruit_bowl_rotten
    @fruit_bowl = FruitBowl.new(:rotten => true)
    assert_nil @fruit_bowl.apple
  end

  def test_pear_should_be_nil_if_fruit_bowl_rotten
    @fruit_bowl = FruitBowl.new(:rotten => true)
    assert_nil @fruit_bowl.pear
  end

Using the wonderful Ruby methods define_method and send you can dry this up really easily like thus:

# Put the different bits into an array
[:banana, :apple, :pear].each do |fruit|
  # Iterate over the array and create a test method for each object.  Remember to prefix the method with 'test' when using Test::Unit
    define_method "test_#{fruit}_should_be_nil_if_fruit_bowl_rotten" do
      @fruit_bowl = FruitBowl.new(:rotten => true)
      # And use the send method to check the correct attribute
         assert_nil @fruit_bowl.send(fruit)
    end
  end

Tip Four – Test the objects behaviour

Here’s a tip that should revolutionise your testing methodology forever…

Make sure you test the correct behaviour for an object

What this means is that when testing an object, don’t create a test that looks outside of its scope.

For example:

Up until very recently I used to be a big fixtures man. It wasn’t uncommon for me to write functional tests like this:

    def test_transfers_heading_should_be_on_page
      get :show, :id => festivals(:festival_with_transfers_heading)    
      assert_select 'h3#heading', "Transfers" 
    end

Which was to test that the h3 with the id of ‘heading’ displayed the correct text, in this case “transfers”.

But deep down inside I was relying on this test to do more than what it should be doing. Whenever this test was run it had to load the festival from the database, and show the correct template. However, the scope of the test though was just to test that the correct information was shown in the view, anything else was irrelevant to this test.

A better test could look like this:
    def test_transfers_heading_should_be_on_page
      festival = Festival.new(:heading => 'Transfers')
      Festival.stubs(:find).returns(festival)
      get :show, :id => 1
      assert_select 'h3#heading', "Transfers" 
    end

The database doesn’t get hit because it doesn’t need to get hit. All we’re testing here is that the correct heading is displayed on the page – that the controller fulfilled its obligation by marshalling the data from the model to the view.

Tip Five – Expectations are tests too you know!

Mocks Rock – there I said it!

But, you can get so obsessed with stubbing out all manner of stuff for your assertions that you can mess out on all the nice inbuilt assertions built into mock objects – expectations.

For example, you might have an ActiveRecord observer that does some stuff after a model is saved like this:
  class FruitObserver < Observer

   def after_create(fruit)
     # Add to all the inventory lists
     InventoryList.find(:all).each do |i|
       i.stock.push fruit
     end    
   end
  end
You could use a mocha expectation to test this without using a single assert_blah like this:
  def test_when_creating_banana_observer_is_called
    banana = Fruit.new(:name => "banana")
    InventoryList.any_instance.expects(:push).with(banana)
    banana.save
  end

You don’t need to have assert_blah in all your tests – this is a perfectly acceptable test because it includes the expectation InventoryList.any_instance.expects(:push).with(banana).

assert_select the order of a drop-down in Ruby on Rails

Posted on January 14, 2008

To write a functional test to determine the correct order of a dropdown do the following:

    option = css_select("select#id_of_dropdown option")[n]
    assert_equal "Value To Compare", option['value'].to_s

And replace id_of_dropdown with the dom id for the drop down you’re looking for, replace n with the nth number, and replace ‘Value To Compare’ with the value you’re looking for.

So for example, if you have a dropdown with an id of ‘fruit’ and you want to make sure that the third option is ‘banana’ use this:
    option = css_select("select#fruit")[3]
    assert_equal "banana", option['value'].to_s

Default columns in fixtures

Posted on January 03, 2008

Ever find yourself writing out the same old column names in fixtures just so your tests will work? Ever wonder if there’s a better way? Well there is!

Fixtures, now with NEW default columns!

All you need to do is bung a method in your test/test_helper.rb file that looks like this:

  def default_columns
    columns = <<END_OF_STRING
    created_at: 1 Jan 2007
    created_by: 1
    updated_at: 1 Jan 2007
    updated_by: 1
    lock_version: 1
  END_OF_STRING     
  end

Now, in each fixture that requires the default columns put this at the top:

<% require "#{RAILS_ROOT}/test/test_helper.rb" %>                    
And then for each fixture bung in this inline code:
one:
  id: 1   
  name: MyString
<%= default_columns %> 

Voila! No more repeating yourself with stupid columns! Hooray!

Note: spacing is important in yaml files – so make sure you put the <%= default_columns %> in an appropriate place

For Loop in SQL Server 2000

Posted on August 24, 2007

Here’s a just-so-crazy-it-might-work way of doing a for loop in sql server.

This comes in real handy when making fake data for testing purposes.

DECLARE @count INT
DECLARE @command_to_loop NVARCHAR(1000)
-- This example runs from 1 to 10
SET @count = 1
WHILE(@count <= 10)
BEGIN
    -- This is just my crazy example
    -- Replace @command_to_loop with whatever you want to execute loop
    SET @command_to_loop = 'insert into customers (name) values 
                       (''Billy Bob' + cast(@count as varchar(3)) + ''')'
    print @command_to_loop
    EXEC sp_executesql @command_to_loop
    SET @count = @count + 1
END

Quick and dirty deny_select

Posted on June 24, 2007

I’ve just been learning how incredibly powerful Test First Development is in Rails – being able to test for elements on the dom using assert_select is fantastico!

However I note that there’s no opposite – a deny_select if you will. Something that will test to see if a given element does NOT exist.

Here’s one I cooked up earlier:


  def deny_select(element, message)
    assert_select element, false, message
  end

Use it by passing in the css element id and the message if the assertion fails like this:


  deny_select 'table.calendar', "Calendar should not be on page" 

Better structured Selenium Tests

Posted on May 28, 2007
Selenium is a fantastic tool developed by ThoughtWorks for automated web acceptance tests.

If you're a web developer and you've never used this tool, go check it out immediately!

How I've been generally Selenium is by using the Selenium IDE Firefox Plugin to record my tests - and then copying the automatically generated C# into my test classes.

This is a really quick and easy approach, however the test code get's REALLY ugly REALLY fast.

Then I saw Erik Doernenburg's excellent presentation on Selenium best practices, and heard a great tip that's so simple and elegant I felt stupid for not thinking about it before.
Create a class for each of your web pages and use static methods to wrap the Selenium code. This allows you to create nice, human readable tests that are also far less brittle.
MainPage.cs (C#)
public class MainPage
{
    public static void Open()
    {
      Selenium.Open(@"/");
    }

    public static void SearchFor(string searchString)
    {
      // Ugly brittle code nicely wrapped up
      Selenium.Type("txtSearch", searchString);
      Selenium.Click("btnSubmit");
      Selenium.WaitForPageToOpen("30000")
    }
   
    public static int NumberOfSearchResults()
    {
      // This function uses regex to return the number of results on the page
      ..
    }
}
And then in your unit test class:
MainPageUnitTests.cs (C#)
   [Test]
   public void TestForSearch()
   {
     // Nice readable test logic
     MainPage.Open();
     MainPage.SearchFor("bananas");
     Assert.AreEqual(1, MainPage.NumberOfSearchResults(), "Wrong number of results");
   }