Testing dates and timezones using Jest
Without setting a global TZ
environment variable, and being able to fake the current system time to keep tests stable
Preface
There are already a lot of posts covering testing dates and timezones in JavaScript, but all the ones I encountered were either too basic, too overkill (running all tests with the TZ
environment variable), or didn’t cover mocking both the date and the timezone.
Dates are hard, and time zones are harder, especially when done in JavaScript. There are multiple libraries to work around the difficulties of using Date
, and a proposal to replace it with something more modern.
Testing these can also be difficult, since you may want to test edge cases (e.g. leap year), or multiple timezones. Most solutions to this issue in the JavaScript ecosystem focus on one of 3 things:
- Faking the system time and allowing you to manually control it and trigger timers (e.g.
setTimeout
,setInterval
etc.). Without waiting for the actual time specified to pass. - “Locking” the system time to some predetermined value, so you can perform your tests on it. For example, if you’re writing a date picker, you may want to “lock” the time to a given date, and then make sure that the picker behaves as expected, in that time. Otherwise, your
expect
s become harder to reason about, since they’re dynamic by nature. - Changing the timezone of the system, so you can test how your code behaves in different environments. This is especially relevant in web apps, where you don’t have control over the environment where the code runs, since it runs on users‘ machines.
Each of these issues has multiple, isolated solutions in the form of npm packages or ways of running your tests:
Manually controlling timers
Jest has pretty good built-in timer mocks, and Sinon.js’ fake timers are also very good (and easier to use, from my experience).
See their docs for examples, are there is a lot to cover, and they do a great job at it!
Locking system time
For locking the system time you can use mockdate
, which lets you specify a date-time in a test, after which subsequent usages of Date
would use that time:
Faking the timezone
There are two main approaches to solving the issue of faking a timezone in tests:
- Set the
TZ
environment variable before running Jest:
TZ=UTC jest
This will cause all tests to run under the UTC timezone. Regardless of whether or not the test is even timezone-related. This is even more of a problem if you want to test multiple timezones. You’ll have to resort to solutions like:
TZ=UTC jest && TZ=America/New_York jest
(or running them concurrently using something like concurrently
), but again — even if only a few of your tests require timezone testing, you’re running all of them for all time zones, and also face the fact that the test code is not aware of the timezone it's running in.
2. Use a package like timezoned-date
, which lets you create a new Date
constructor, with a dynamic offset
:
is works great, just remember to save the original Date
before overriding it and restore it afterward:
Locking system time AND setting the timezone
Using mockdate
and timezoned-date
together does pose an issue that might not be obvious — since both (directly or indirectly) override the global Date
, the implementation matters — especially in mockdate
‘s case, which overrides the resets the original Date
itself. At the time of this writing, the implementation saves Date
when the module is first require
-ed:
// mockdate.ts
const RealDate = Date;
This means that this code wouldn’t behave as expected:
As you can see — mockdate
worked fine, but didn’t take the Date
the constructor we just created & overrode using timezoned-date
into account.
To fix this we can create a test utility function that works around the issue while abstracting the problematic implementation, as well as simplifies the test code:
Usage is then pretty simple:
Conclusion
I hope this post helped you get a better understanding of the various ways to test dates in JavaScript, especially when you use 3rd party modules, which rely on global variables and don’t get their dependencies injected (e.g. using Date
).
The implementation of setupMockDate
introduced above should be generic enough to cover different use-cases, and might benefit from being published to npm directly to save you from having to copy-paste this into your codebase. If I do publish it, I’ll be sure to update this blog post.