Matteo Piazza
Matteo Piazza

Reputation: 2502

Jest: test Intl.DateTimeFormat

I would like to test a filter function I wrote which return a date formatted using Intl.DateTimeFormat('en-GB', options):

// module "date.js"
export default function (dateISOString) {
    const options = {
        year: 'numeric',
        month: '2-digit',
        day: '2-digit',
        timeZone: 'UTC'
    };
    let d = new Date(dateISOString);
    return new Intl.DateTimeFormat('en-GB', options).format(d);
};

This is my test, using Jest:

import date from '@/filters/date';
describe('date', () => {
    it('should format the date into dd/mm/yyyy', () => {
        expect(date('2014-02-11')).toEqual('11/02/2014');
    });
});

but it fails with:

Expected value to equal:
  "11/02/2014"
Received:
  "02/11/2014"

Is it possible to test (or mock) the Intl API with Jest? It looks like the problem is due to a different behaviour of the Impl API in the browser and in a Node environment.

Upvotes: 25

Views: 17275

Answers (5)

evanjmg
evanjmg

Reputation: 3795

These are all convoluted answers. The simplest is extending the prototype like so in jest. Modify any property or function with define property

Object.defineProperty(Intl.DateTimeFormat.prototype, 'formatToParts', {
  value: () => ['day', 'month', 'year'].map((type) => ({ type })),
})

Upvotes: 0

aaronmgdr
aaronmgdr

Reputation: 608

Just found a solution if you want to change the zone per test.

The benefit here is minimal change to the Intl object since we are just using the normal api to set a default

const timezoneMock = function(zone: string) {
    const DateTimeFormat = Intl.DateTimeFormat
    jest
    .spyOn(global.Intl, 'DateTimeFormat')
    .mockImplementation((locale, options) => new DateTimeFormat(locale, {...options, timeZone: zone}))
  }

  afterEach(() => {
    jest.restoreAllMocks();
  })

and then

  describe('when Europe/London', () => {
    it('returns local time', () => {
      timezoneMock('Europe/London')
   //etc.... 

Upvotes: 11

techmsi
techmsi

Reputation: 443

In package.json add the intl package

 "intl": "*",

In the jest.config.js

module.exports = {
    moduleNameMapper: {
        Intl: '<rootDir>/node_modules/intl/'
    }
};

Then in Date.spec.js

 describe(`Date by locale`, () => {
     beforeAll(() => {  global.Intl = require('intl'); });
    // Add your tests here. 
    // Add a temporary console.log to verify the correct output
 }

Upvotes: 2

Sergei Ryzhov
Sergei Ryzhov

Reputation: 51

You can use polyfill, as described here

import IntlPolyfill from 'intl';
import 'intl/locale-data/jsonp/ru';

if (global.Intl) {
    Intl.NumberFormat = IntlPolyfill.NumberFormat;
    Intl.DateTimeFormat = IntlPolyfill.DateTimeFormat;
} else {
    global.Intl = IntlPolyfill;
}

Upvotes: 3

Steve Vaughan
Steve Vaughan

Reputation: 2189

The only solution that I managed to find to this problem was to install full-icu which seemed to provide the right locales to node during the testing process.

That package is needed because node by default only ships with a limited set of locales, explained here: https://nodejs.org/docs/latest-v9.x/api/intl.html

With that package installed, the additional step that I had to take was to change my test command to use:

"test": "NODE_ICU_DATA=node_modules/full-icu jest --config jest.config.js"

I ran into this problem in a couple of different environments. When running the tests locally on Mac OS and also when running the tests inside a Docker container during CI.

Interestingly, I don't need to use the export when running the tests via WebStorm's Jest integration. It definitely seems like the behaviour of the Intl library is far from stable in node.

Upvotes: 18

Related Questions