JavaScript ES6 Intl not working properly when running Jest tests

JavaScript Intl

ES6 ships a namespace to handle internationalization, it covers cases like:

  • Number formatting.
  • Date formatting.
  • Language-sensitive string comparisons.

For instance, if we want to format a currency using es-ES culture (Spain), we can easily create a formatted output by coding something like:

const value = 200;
const formattedValue = new Intl.NumberFormat("es-ES", {
  style: "currency",
  currency: "EUR"
}).format(value);

console.log(formattedValue);

This code should output a string like:

200,00 €

Ok cool, but if I write a Jest test to check this piece of code, it will fail; instead of generating the following output: 200,00 € it will generate €200.00 :-(, what's happening here?

The issue

Let's write the following function:

export const formatCurrency = value =>
  new Intl.NumberFormat("es-ES", {
    style: "currency",
    currency: "EUR"
  }).format(value);

Let's add a test to check if it's working properly

import { formatCurrency } from "./sample-formatter";

it("Display a number in es-ES currency format", () => {
  // Arrange
  const value = 200;

  // Act
  const formattedText = formatCurrency(value);

  // Assert
  expect(formattedText).toEqual("200,00 €");
});

What result do we get? Agggh! Test failing...

assert fail

It seems that the test execution is not taking into account the Intl es-ES configuration!

What's going on here

Jest tests don't run on top of the browser; they run under Node.js, which doesn't ship the full internationalization support by default. You can find more info about this issue in this link.

How can we fix that?

TL;DR;

If you are in a hurry, here you have a quick solution:

  • First install the full node ICU library as a project local dependency:
npm install full-icu --save-dev
  • Then install a cross platform package to allow consistently setting environment variables in both Windows and Linux:
npm install cross-env --save-dev
  • Now update your package.json test command to set up this ICU Environment variable (it should point to the nodemodules/full-icu_ path)

If you are on a custom solution

./package.json

- "test": "jest --verbose",
+ "test": "cross-env NODE_ICU_DATA=node_modules/full-icu jest --verbose",

If you are on a create-react-app based project

./package.json

  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
-    "test": "react-scripts test",
+   "test": "cross-env NODE_ICU_DATA=node_modules/full-icu react-scripts test",
    "eject": "react-scripts eject"
  },

And you're good to go! Execute npm test and check that the test is passing now. You can find the full implementation in this repo .

If you want to learn more about how this works (plus bonus point: how to set this up for VS Code debugging), keep on reading :).

Possible solutions

Node.js doesn't ship by default a full ICU installation (International Components for Unicode). The reason behind this is that most Node.js users will make use of only a small portion of ICU functionality. Only a subset of the full ICU data set is provided by Node.js by default.

What options do we have available?

  • We can make a build of Node.js that includes full ICU support: it doesn't seem a straightforward solution, and you'd better think twice about asking other developers in your team to use your own nodejs build (or configuring your Continuous Integration Server).
  • You can install the package globally full-icu: making your project dependent on global package install is not a good idea though, and maybe not very Continuous Integration friendly either.
  • You can install full-icu as a project dependency. By doing so, you can both get your tests working and avoid using a Nodejs custom build or having a global dependency installed.

Then... the solution seems to be easy: just install it locally and Bob's your uncle? Well, it's not that straightforward. You need to set up some plumbing before running your Jest tests to ensure Node.js is using full-icu

Chosen Solution

Let's start by installing full-icu in our project:

npm install full-icu --save-dev

When we install this package we get an interesting message:

Node will use this ICU datafile if the environment variable NODE_ICU_DATA is set to “node_modules/full-icu”

So, we need to set up the environment variable right before running our test battery.

If we want to set up these variables in a consistent way (so it works on Windows and Linux platforms), we need to make use of the cross-env library. Let's install it:

npm install cross-env --save-dev

We need to set up an environment variable in our test command that will indicate Node.js the path to the full-icu implementation.

Let's update our package.json test command and include the environment variable before running the Jest test battery.

./package.json

- "test": "jest --verbose",
+ "test": "cross-env NODE_ICU_DATA=node_modules/full-icu jest --verbose",

If you start the test command you can check that the test is passing:

npm test

test passing

Debugging on VS Code

That was great, but I use Visual Studio Code to debug my jest tests... how can I set this up for VS Code? You can just add the following environment config entry in the launch.json file:

./.vscode/launch.json

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Jest single run all tests",
+      "env": { "NODE_ICU_DATA": "node_modules/full-icu" },
      "program": "${workspaceRoot}/node_modules/jest/bin/jest.js",
      "args": ["--verbose", "-i", "--no-cache"],
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen"
    }
  ]
}

If you need more info about how to debug jest unit tests using Visual Studio Code, check this article.

What about a create-react-app based app?

To make this work on a project created by using create-react-app you have to follow almost the same steps; the only step that is slightly different is tweaking the test command on your package.json file.

Let's start by installing full-icu in our project:

npm install full-icu --save-dev

So, we need to set up the environment variable right before running our test battery.

If we want to set up these variables in a consistent way (so it works on Windows and Linux platforms), we need to make use of the cross-env library. Let's install it:

npm install cross-env --save-dev

Now we need to set up an environment variable in our test command that will instruct Node.js the path to the full-icu implementation.

Let's update our package.json test command and include the environment variable before running the Jest test battery.

./package.json

  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
-    "test": "react-scripts test",
+   "test": "cross-env NODE_ICU_DATA=node_modules/full-icu react-scripts test",
    "eject": "react-scripts eject"
  },

Resources

If you want to learn more about ES6 Intl you can check MDN official documentation.

For Node.js and Intl support you can check Node.js official documentation node.js intl

We have published both custom and create-react-app examples of this post in this Github repo.

Wrapping up

Jest is great, but you have to bear in mind that it doesn't run the tests on the browser. This may lead to different behaviour patterns in certain scenarios, differences which could become critical not only for Jest based testing, but also for server side rendering based projects.

Fortunately there are workarounds available to overcome these limitations.

About Basefactor

We are a team of JavaScript experts. If you need coaching or consultancy services, don't hesitate to contact us.

Doers/

Location/

C/ Pintor Martínez Cubells 5 Málaga (Spain)

General enquiries/

info@lemoncode.net

+34 693 84 24 54

Copyright 2018 Basefactor. All Rights Reserved.