Jason Earl
Personal Website
BLOG
Testing Time Dependent Code
The Problem
Writing unit tests is a great way to ensure your code works and ensure that other programmers don’t break your code by accident. Not only that the whole testing philosophy seems to encourage you to think about making your code as modular as possible in order to be make it testable, thus making it more robust architecturally.
However, difficulties often seem to arise with when testing pieces of code that are strongly associated to any sort of I/O with external systems, or anything else that is tied to some sort of global side effect, such systems need to use randomisation or has time dependencies. Over the years most test frameworks have managed to over come these issues through the use of Mock Objects.
One issue though seems to haunt me a lot is dealing with systems which trigger events based on the system time. For instance credit based billing systems where the amount you pay for a service is based on the amount of time you actively use it. A good example would be property website where users will get billed a fixed amount for each active property that have listed on a monthly basis. In a test scenario we need to be able create a property, and simulate going into future by a set period of time and ensure that our system is able to pick up and bill our user for the time period we’ve moved forward by.
A Possible Solution
Thankfully, Ruby’s dynamic ability to override core features allows us to rewrite how the Time class behaves in a test environment. This can easily be done by making the Time.now method return a different time if we have decided to manually override the time. A piece of sample code you could use for in your Test/Spec helpers might be:
Source Listing:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class Time # Holds the fake times @@active = nil cattr_accessor :active class << self # Return fake time if the time has been overriden def now active || new end # Set the time to be fake for a given block of code def is(new_time, &code) old_time = active self.active = new_time begin code.call if code ensure self.active = old_time end end end end |
How It Works
This code should be fairly simple to understand for those who know Ruby. Basically we use a class variable to hold the overridden time value. If this is not set it will get it from the system clock via the Time.new
method which does the same thing as the old Time.now
method. While we can override the time using the class writer for Time.active=
, it is preferable that we use Time.is
as this protects us from possible control flow issues that arise from not resetting the time back if pre maturely exit our code, either by accident through programmer error (i.e. returning from a function), or because of an Exception / failed test case. The use of blocks + ensure handles this in a similar way to RAII in C++.
Example Usage
As stated above, we should protect ourselves by using the Time.is
method in critical areas, or at least use Time.active = nil
in the tear down process of our test, otherwise we run the risk of affecting other our test cases incorrectly.
Below shows a basic pseudo code example for the property scenario I outlined earlier:
1 2 3 4 5 6 7 |
property = Property.create # Put our attributes here property.months_billed.should == 1 Time.is 3.months.since do property.bill! Time.active = 1.hour.since property.months_billed.should == 4 end |
Listening to Rock:
Be Quiet And Drive (Far Away)
- Deftones