Monday, July 31, 2006

Redefine DateTime.now for consistent test results

Today I was looking at some code that used a class called Environment. The Environment class was used to specify a date based on what environment the code was executing in. In production the Environment class would simply return Datetime.now; however, when Environment was used in a test it always returned the value DateTime.new(2006, 1, 23, 13, 14, 15).

This can be particularly important if you are working with code similar to this:
class Comment
attr_reader :created_at

def initialize
@created_at = DateTime.now
end
end
In the example, you would like a test that proves that the created_at instance variable is set in the constructor, such as:
class CommentTest < Test::Unit::TestCase
def test_ctor_inits_created_at
assert_equal DateTime.now, Comment.new.created_at
end
end
Unfortunately, this test doesn't work correctly if the DateTime instances aren't created at exactly the same point in time.

To solve this problem the Environment class was designed to allow you to specify a date in the expected results of a test without being concerned if it matched the current date or not. The Environment solution worked, but it wasn't particularly elegant.

We decided to refactor to what we thought was a simpler solution. We added the following code directly to the test class.
class << DateTime
def now
DateTime.new(2006, 1, 23, 13, 14, 15)
end
end
By redefining the now method we were able to reliably assert equality. This change removed the need for the Environment class also. Of course, DateTime.now will always return this date for all the tests that follow this one; we were comfortable with all the tests depending on this constant value.

1 comment:

  1. Anonymous6:53 AM

    Have you seen Stubba? You could do...

    class CommentTest < Test::Unit::TestCase

    def test_ctor_inits_created_at
    now = DateTime.now
    DateTime.stubs(:now).returns(now)
    assert_equal now, Comment.new.created_at
    end

    end

    - The DateTime class reverts to its original behaviour after each test.
    - You can specify different return values in different tests.
    - The test is more explicit, because the return value is defined in the test.

    ReplyDelete

Note: Only a member of this blog may post a comment.