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:

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

def test_average_computes_correctly
assert average(@records) == 2
end
end
[/sourcecode]

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:

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

test "average computes correctly" do
assert average(@records) == 2
end
end
[/sourcecode]

Written as a callback, that looks like:

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

test "average computes correctly" do
assert average(@records) == 2
end
end
[/sourcecode]

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.

Post a Comment