Update: This doesn't produce deterministic results if you want to dynamically change the TZ but it will work if you only want one TZ. It could be an alternative to specifying at the script level, but I think that would be the better answer here.
The issue is that by setting process.env.TZ
at runtime, it will bleed across tests creating non-deterministic behavior (side effects) during a regular Jest test run. It may work if you use --runInBand
which runs tests serially but I wouldn't count on it.
I also found an old archived issue about dynamic timezones in Node and it looks like dynamically adjusting it won't work in general.
Instead I will probably end up with multiple scripts that each set TZ
before launching jest
.
For my use case, I actually wanted to run tests under different timezones for specific date-based edge cases. Sometimes users run into timezone-based bugs and we want to cover that easily within our test suites.
We run all tests in the project by default using one of the proposed answers here, by setting TZ=UTC
in the npm
script (e.g. TZ=UTC npm run jest
. This runs all tests under the UTC timezone.
Then, we leverage the testEnvironment
configuration which can be set at the test suite-level using the JSDoc pragma @jest-environment
. Using this custom test environment, we can then read the suite's desired timezone using a "custom docblock pragma" like @timezone
. This enables timezone customization per-test-suite which is not as ideal as per-test but good enough for our purposes.
jsdom-with-timezone.js
const JSDOMEnvironment = require('jest-environment-jsdom');
/**
* Timezone-aware jsdom Jest environment. Supports `@timezone` JSDoc
* pragma within test suites to set timezone.
*
* You'd make another copy of this extending the Node environment,
* if needed for Node server environment-based tests.
*/
module.exports = class TimezoneAwareJSDOMEnvironment extends JSDOMEnvironment
{
constructor(config, context) {
// Allow test suites to change timezone, even if TZ is passed in a script.
// Falls back to existing TZ environment variable or UTC if no timezone is specified.
// IMPORTANT: This must happen before super(config) is called, otherwise
// it doesn't work.
process.env.TZ = context.docblockPragmas.timezone || process.env.TZ || 'UTC';
super(config);
}
};
tz-eastern.test.js
/**
* @timezone America/New_York
*/
describe('timezone: eastern', () => {
it('should be America/New_York timezone', () => {
expect(process.env.TZ).toBe('America/New_York');
expect(new Date().getTimezoneOffset()).toBe(300 /* 5 hours */);
});
});
jest.config.js
module.exports = {
"testEnvironment": "<rootDir>/jsdom-with-timezone.js"
}
Using this with jest.useFakeTimers('modern');
and jest.setSystemTime()
is sufficient for more robust date testing so I thought I'd share this approach for others to benefit from! Since the pragma handling is custom, you could customize this any way you like for your use case.
Sources: