It's good practice in unit testing to assert any preconditions (aka assumptions) relevant to the test and the expected postconditions (aka side-effects). I had a unit test that went something like this:
class TaggableTest < Test::Unit::TestCase
fixtures :tags, :items
def test_tagging
one = items(:one)
one.tag_list = "tag1, tag2, tag3, tag4"
assert_equal 0, one.tags.size
assert_equal 3, Tag.count
assert_equal 0, Tagging.count
one.save
assert_not_nil one.id
assert_equal 4, Tagging.count
assert_equal 4, Tag.count
assert_equal 4, one.tags.size
end
I didn't care for this code because of the redundancy of the assert equals. So I added some new asserts to TestCase in test_helper.rb and was able to convert it into the following:
def test_tagging
one = items(:one)
one.tag_list = "tag1, tag2, tag3, test4"
assert_changed(lambda {one.tags.size},
lambda {Tag.count},
lambda {Tagging.count},
:from => [2, 3, 2],
:to => [4,4,4]) { one.save }
end
assert_changed will execute the code blocks passed in prior to the execution of the primary block and again afterwards. It will then compare those values to the from and to values (optionally) passed in. If you don't pass in both :from and :to, assert_changed merely validates that the values changed.
private
def singleton_maybe(s)
return s[0] if s.is_a?(Array) and s.size == 1
return s
end
public
def assert_array_or_svo_equal(expected, actual)
expected = singleton_maybe(expected)
actual = singleton_maybe(actual)
assert_equal expected, actual
end
def assert_changed(*expressions)
options = {}
options.update(expressions.pop) if expressions.last.is_a?(Hash)
initial = expressions.map {|e| e.call}
assert_array_or_svo_equal options[:from], initial if options.has_key?(:from)
yield
subsequent = expressions.map {|e| e.call}
initial.each_with_index { |i, n| assert_not_equal i, subsequent[n] } if not (options.has_key?(:to) and options.has_key?(:from))
assert_array_or_svo_equal options[:to], subsequent if options.has_key?(:to)
end
def assert_unchanged(*expressions)
initial = expressions.map {|e| e.call}
yield
subsequent = expressions.map {|e| e.call}
initial.each_with_index { |i, n| assert_equal subsequent[n] }
end