Time Is Relative
Date related properties in business applications are often involved in business rules. Business applications are scattered with date based triggers and validations. At the unit test level lots of useful libraries have made handling time at the unit test level easier in recent years. But without fancy mocking behaviour in acceptance or E2E test it still proves difficult to get reliable testing in place.
The reason for this is that creating proper example data with dates for a testing environment is difficult. In recent year our software engineering have moved up in the cloud stack. We are increasingly becoming users of a platform instead of owning the infrastructure. System clocks are not in our influence, and generating example data sets is error prone. As time passes behaviour will start to become ‘unexpected’.
Recently I created Testdate, an NPM package to give time control back to the developer. This library brings the definition of a date back to a specification relative to current time.
yarn global add @endran/testdate
Why travel time?
Say you want to assert in your E2E test that people are grouped by age. The test set contains many people, these test run multiple times a day, and they need to pass today, but also tomorrow. Every day that passes might be some test persons' birthday, and you do not want to address this in the test code.
You are faced with two possible solutions. Either you make sure that the environment that executes the code has a modified time, so that it will always run on the same day, forever stuck on that day. Or, evolve the data and test the setup under the present day time. I thinks it's clear that the latter is desirable, since it's more similar to the production environment. And freezing time isn't always an option, like in a cloud stack.
Evolve your data
How to evolve our data? Let’s look at an example. Don't worry if the syntax seems complex at first sight, it's fairly easy and we will break it down step by step.
Say we have a test data set that looks like below called dataset.testdate.json:
{
"ageString": "__TESTDATE_now-0030-07-14_TESTDATE__",
"ageEpoch": "__TESTDATE_now-0030-07-14#X%1_TESTDATE__"
}
Now we run Testdate like bolow. FYI, today is March 15, 2019:
testdate --source 'dataset.testdate.json' \
--destination 'dataset.evolved.json' \
--format LLLL
The output will be in dataset.evolved.json:
{
"ageString": "Monday, August 1, 1988 3:16 PM",
"ageEpoch": 586444596
}
Break it down
Marker: We start with __TESTDATE_<content>_TESTDATE__ markers, there can be at max 1 marker set per line, and both start and en marker must be on the same line. All content between the markers will be eligible for time travel.
Direction: We can travel backwards now- for birthdays, and forwards now+ for due dates.
Time span: 0030-07-14 is the yyyy-MM-ddThh:mm:ss notation of the travel time. So in this particular case it's 14 days, 7 months and 30 years ago. Only yyyy is mandatory, everything else is optional. Instead of T you can also use @, this allows for time travelling the date, but fixing the time. The ageString output for 0030-07-14@19:00:00 would be Monday, August 1, 1988 7:00 PM.
Format: The desired output format #<format>, according to Moment.js. Be sure to take a look at this cheatsheet. There is also a mandatory --format param, which will be used as a default when #<format> isn't provided.
Punctuation marks: This example uses json as it's format, and valid json required " marks around string. If we convert to number these need to be stripped, that's where %<numberOfMarks> comes in.
Further usage
Testdate is compatible with any text file type. For example .csv, .txt, .myCustomType and .json. As long as it's text, it can handle it.
The minimal valid input is. This result will be 'now'.
__TESTDATE_now-0000_TESTDATE__
When you do not need a custom format, but you do need punctuation marks, the # sign is still required. The correct notation is #%1.
See the README of Testdate for many more variations.
Cover image by Vitorio Benedetti.