Getting Jest to work with your ESM-based npm library for React Native

Getting Jest to work with your ESM-based npm library for React Native

Introduction

It was all so easy. Simply run the react-native init command, set the preset in your Jest config as 'react-native', and voila, npm test works like a charm. But, things aren't as rosy when you want to publish npm libraries and Jest is your chosen test suite.

While we all love the modern ESM import from and export default syntax, Jest - not so much. It is very much still in love with CommonJS syntax. This along with the fact that react-native code uses Flow, made my life not-so-easy when trying to publish my npm library. It wasn't just me experiencing these issues. Based on these discussions - Issue 152, Issue 10111 - many of my fellow devs were facing similar roadblocks.

To introduce some order to all the above chaos, here are some steps to get Jest working with your ESM-based npm library for React Native:

Setting up your package.json

{
{
    "name": "my-react-native-library",
    "version": "1.0.1",
    "description": "A useful library",
    "main": "dist/index.js",
    "scripts": {
      "test": "jest",
      "prepare": "npm run build",
      "build": "BABEL_ENV=production babel <folder-path-with-your-code> --out-dir dist",
    },
    "keywords": [
      "useful"
    ],
    "author": "Ronil Mehta",
    "license": "ISC",
  "dependencies": {
    "@react-native-async-storage/async-storage": "~1.15.0",
    "react-native-get-random-values": "~1.7.0",
    "uuid": "^8.3.2"
  },
  "devDependencies": {
    "@babel/cli": "^7.18.9",
    "@babel/core": "^7.18.9",
    "@babel/preset-env": "^7.18.9",
    "@babel/preset-flow": "^7.18.6",
    "jest": "^27.5.1",
    "jsdoc-to-markdown": "^7.1.1"
  }
  }
}

Setting up your babel.config.js

module.exports = {
    presets: [
        "@babel/preset-env",
        "@babel/preset-flow"
    ],
    env: {
        production: {
            ignore: [
                "<folder-path-with-your-code>/**/*.test.js"
            ]
        }
    }
}

It's important to know that babel runs the presets array from right to left. Meaning, it will first run @babel/preset-flow and then @babel/preset-env. @babel/preset-flow first converts any Flow syntax in react-native to ESM. @babel/preset-env then converts this ESM code to CommonJS, which is then used by Jest.

Note: Jest had issues when the babel configs were in .babelrc or in package.json. Please make sure to write your babel config in babel.config.js.

Setting up your jest.config.js

Run npx jest --init in the project root directory to begin the config file generator wizard. The following are the inputs you should provide:

$ npx jest --init
The following questions will help Jest to create a suitable configuration for your project
✔ Would you like to use Typescript for the configuration file? … noChoose the test environment that will be used for testing › jsdom (browser-like)
✔ Do you want Jest to add coverage reports? … yes
✔ Which provider should be used to instrument code for coverage? › babel
✔ Automatically clear mock calls, instances and results before every test? … yes

Once the config is generated, set preset: 'react-native'.

By default, Jest ignores libraries in node_modules when transforming code using babel. But since react-native contains Flow code that needs to be transpiled, a special regex needs to be added to transformIgnorePatterns in the config. One of the most important things preset: 'react-native' does is force Jest to transform the react-native libraries innode_modulesto a form Jest can understand. It does this by presetting the value oftransformIgnorePatternsinjest.config.js`.

Running Jest

Now that you have carefully followed the above instructions and set up the configs, it is time to run some unit tests: Execute npm test and hopefully, you see a whole lot of green text!