jest: using import and es modules

2021-05-03

 | 

~2 min read

 | 

311 words

If you are taking advantage of ES Modules, whether through a .mjs extension or babel transpilation, it’s likely that you’re using the import/export syntax. This can pose problems for Jest which defaults to using Node’s require syntax.

A quick fix for this problem, assuming babel is responsible for the transpilation (and perhaps Webpack is responsible for tree-shaking), is to check whether tests are being run before determining who should transpile the modules.

For example, imagine the above scenario: babel owns transpiling, but defers to Webpack for tree-shaking, and so doesn’t handle the conversion to modules. You might have something like the following:

.babelrc.js
module.exports = {
    presets = [
        ['@babel/preset-env',{modules: false}]
    ],
}
webpack.config.js
const path = require("path")

module.exports = {
  resolve: {
    modules: ["node_modules", path.join(__dirname, "src"), "shared"],
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: "babel-loader",
      },
    ],
  },
}

Here, we explicitly tell Babel to ignore modules and then resolve them with Webpack. The issue is that if we wanted to use one of our methods within a Jest test, we’d be forced to use require. Without it, we’ll get a syntax error:

src/__tests__/utils.js
import { myUtility } from "../utils.js"
//...
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){import { getFormattedValue } from './utils';
                                                                                             ^^^^^^

    SyntaxError: Cannot use import statement outside a module

      at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1350:14)

The fix is straightforward: interrogate the NODE_ENV to determine if we’re running tests, and if we are, pull the module resolution into Babel’s purview. If we’re not, allow Webpack to continue owning this responsibility.

.babelrc.js
+ isTest = process.env.NODE_ENV === 'test'

module.exports = {
    presets = [
-         ['@babel/preset-env',{modules: false}]
+         ['@babel/preset-env',{modules: isTest ? 'commonjs' : false}]
    ],
}

Because our .babelrc is written in Javascript, this is as simple as assigning a value to isTest and then using a ternary expression. With that change, Jest can now use ES modules!


Hi there and thanks for reading! My name's Stephen. I live in Chicago with my wife, Kate, and dog, Finn. Want more? See about and get in touch!