Mix with Caution: Callbacks and Setup/Teardown Methods

If you’re familiar with unit testing tools (at least the ones based eventually on JUnit), you’ll immediately recognize the concept of testcase hooks, especially for setup and teardown.  All of the testing frameworks for Ruby come with varying degrees of hook expressivity.

Rails provides an extension to the standard Test::Unit framework in the form of ActiveSupport::TestCase.  One of the more powerful additions is callback queues of hooks.  Instead of declaring a single setup method in a TestCase class, you can declare any number of setup and teardown callbacks.

Here’s a simple background example with vanilla Test::Unit that declares a setup hook:

class AverageTest < Test::Unit::TestCase
   def setup
      @records = [1, 2, 3]
   end

   def test_average_computes_correctly
      assert average(@records) == 2
   end
end

The test runner instantiates AverageTestCase, runs setup, and then runs test_average.

ActiveSupport::TestCase inherits from Test::Unit::TestCase, so the same can be stated:

class AverageTest < ActiveSupport::TestCase
   def setup
      @records = [1, 2, 3]
   end

   test "average computes correctly" do
      assert average(@records) == 2
   end
end

Written as a callback, that looks like:

class AverageCallbackTest < ActiveSupport::TestCase
   setup do
      @records = [1, 2, 3]
   end

   test "average computes correctly" do
      assert average(@records) == 2
   end
end

There’s a nasty gotcha in here:   if we subclass AverageCallbackTest, and include another setup hook in the callback, everything works as expected:  both setup hooks run.  But, if we subclass AverageTest, and define a new setup method, the superclass’s setup method will be overridden. 

We recommend that you use callback queues instead of setup methods, where possible.  They create fewer opportunities for subtle failures.


Follow

Get every new post delivered to your Inbox.

Join 369 other followers