Time dependent applications are all around us
Time dependent logic in our applications is quite natural, some examples could be:
- A BI solution - today's reports have to contain the data from the days since the last business day
- An Enterprise Risk Engine - operational risk depends on the age of an exception
- A Social network software - the current age of a message should be displayed, and messages should be displayed in reverse order
Time to time (pun intended) I see implementations where the business logic explicitly uses SYSDATE() in Oracle, or new Date() in Java/Scala, or date in shell script.These make the system very very hard to test can have insidious bugs and also make people very angry.
Bad: Hard coded dependency on the current day/time
Time and speed dependent, flickering tests
Let me show you an example why hardcoded new Date() can be a problem.
On my machine this test failes 2 times out of 10. THE WORST kind of test is the flickering test!
So, why is this happening? The time difference between the new Date() in the Spec and the Wall code, is probably nanoseconds, and equality is checking on the level of milliseconds ("The class
Date represents a specific instant in time, with millisecond precision."). Thus, if we are "lucky" the two Dates will be in the same millisecond, our test passes, but if the Wall code is just after the border of the next millisecond, the test will fail.
Working on a global project I had the pleasure to meet unit tests written on a London machine in a time dependent manner which resulted in a strange situation: all the builds were passing in London (including the build server) but when I moved to New York, I found that on my local machine it's failing.
Time dependent feed file processing
I found that whenever batch processing of feed files is dependent on the date, it is virtually impossible to reuse exactly the same feed files from the past, and I had to copy them, rename them to today's date, even worse if I had to modify the feed files so that the data will be be "up to date" and then run the system.
How easier it would be if I could play time machine and tell the system that I want to run it for a past (or a future!) date?
How do I do it well? Dependency injection!
Version 1: Clock Injection
Philosophical sidenote (the impatient can safely skip this): Some people say that Time is an actor. Until I know better I find it a bit confusing, especially that Actors act, and time is due to the fact that things are in change, the Earth revolves around the Sun, crystals pulsate and we transform the aggregated sideeffect of these changes with change-to-time converter tools (i.e. clocks!) into the man-made scale we call: time.
I like to think about time as a constantly changing value, which is provided by a service called Clock. If we embrace this and introduce a Clock as a collaborator then we'll be able to mock it out, inject stubbed versions, i.e. have control over time.
After all this, an example to control the time in a mocked Clock would be
Testing the Real clock itself
Of course, you can't get away from the problem totally, there is always a part in the system which will have to have hard dependency on time. But similar to any other third party library dependency, you want to contain it, wrap it up, and test it as you can to ensure your wrapper does what it's supposed to do.
In our case, this is the RealClock class, which returns new Date() in its getTime() method implementation.
A good enough way to test it is to ensure that the returned date is between the time of the commands ran before and after getTime.
Version 2: Time Injection
If injecting the Clock service is too much, or it is just impossible in your case, then there is another way, but that will modify your API: injecting time!
This would mean modifying the signature of your method (script, stored procedure, report, etc.), which depends on time, for example in our case, the Wall.message function would have another version with another argument called currentTimeStamp.
- Avoid hardcoded dependency on new Date(), SYSDATE, CURRENT_TIMESTAMP, etc.
- You can choose between two versions of dependency injection to avoid hardcoded time dependency:
- injecting Clock service (collaborator level injection)
- injecting time (method/script signature level injection)