How can I mock Webpack's require.context in Jest?

JavascriptWebpackJestjs

Javascript Problem Overview


Suppose I have the following module:

var modulesReq = require.context('.', false, /\.js$/);
modulesReq.keys().forEach(function(module) {
  modulesReq(module);
});

Jest complains because it doesn't know about require.context:

 FAIL  /foo/bar.spec.js (0s)
● Runtime Error
  - TypeError: require.context is not a function

How can I mock it? I tried using setupTestFrameworkScriptFile Jest configuration but the tests can't see any changes that I've made in require.

Javascript Solutions


Solution 1 - Javascript

I had the same problem, then I've made a 'solution'.

I'm pretty sure that this is not the best choice. I ended up stopping using it, by the points answered here:

https://github.com/facebookincubator/create-react-app/issues/517 https://github.com/facebook/jest/issues/2298

But if you really need it, you should include the polyfill below in every file that you call it (not on the tests file itself, because the require will be no global overridden in a Node environment).

// This condition actually should detect if it's an Node environment
if (typeof require.context === 'undefined') {
  const fs = require('fs');
  const path = require('path');

  require.context = (base = '.', scanSubDirectories = false, regularExpression = /\.js$/) => {
    const files = {};

    function readDirectory(directory) {
      fs.readdirSync(directory).forEach((file) => {
        const fullPath = path.resolve(directory, file);

        if (fs.statSync(fullPath).isDirectory()) {
          if (scanSubDirectories) readDirectory(fullPath);

          return;
        }

        if (!regularExpression.test(fullPath)) return;

        files[fullPath] = true;
      });
    }

    readDirectory(path.resolve(__dirname, base));

    function Module(file) {
      return require(file);
    }

    Module.keys = () => Object.keys(files);

    return Module;
  };
}

With this function, you don't need to change any require.context call, it will execute with the same behavior as it would (if it's on webpack it will just use the original implementation, and if it's inside Jest execution, with the polyfill function).

Solution 2 - Javascript

After spending some hours trying each of the answers above. I would like to contribute.

Adding babel-plugin-transform-require-context plugin to .babelrc for test env fixed all the issues.

Install - babel-plugin-transform-require-context here https://www.npmjs.com/package/babel-plugin-transform-require-context (available with yarn too)

Now add plugin to .babelrc

{
  "env": {
    "test": {
      "plugins": ["transform-require-context"]
    }
  }
}

It will simply transform require-context for test env into dummy fn calls so that code can run safely.

Solution 3 - Javascript

If you are using Babel, look at babel-plugin-require-context-hook. Configuration instructions for Storybook are available at Storyshots | Configure Jest to work with Webpack's require.context(), but they are not Storyshots/Storybook specific.

To summarise:

Install the plugin.

yarn add babel-plugin-require-context-hook --dev

Create a file .jest/register-context.js with the following contents:

import registerRequireContextHook from 'babel-plugin-require-context-hook/register';
registerRequireContextHook();

Configure Jest (the file depends on where you are storing your Jest configuration, e.g. package.json):

setupFiles: ['<rootDir>/.jest/register-context.js']

Add the plugin to .babelrc

{
  "presets": ["..."],
  "plugins": ["..."],
  "env": {
    "test": {
      "plugins": ["require-context-hook"]
    }
  }
}

Alternatively, add it to babel.config.js:

module.exports = function(api) {
  api.cache(true)

  const presets = [...]
  const plugins = [...]

  if (process.env.NODE_ENV === "test") {	
    plugins.push("require-context-hook")	
  }

  return {
    presets,
    plugins
  }
}

It may be worth noting that using babel.config.js rather than .babelrc may cause issues. For example, I found that when I defined the require-context-hook plugin in babel.config.js:

  • Jest 22 didn't pick it up;
  • Jest 23 picked it up; but
  • jest --coverage didn't pick it up (perhaps Istanbul isn't up to speed with Babel 7?).

In all cases, a .babelrc configuration was fine.


Remarks on Edmundo Rodrigues's answer

This babel-plugin-require-context-hook plugin uses code that is similar to Edmundo Rodrigues's answer here. Props to Edmundo! Because the plugin is implemented as a Babel plugin, it avoids static analysis issues. e.g. With Edmundo's solution, Webpack warns:

Critical dependency: require function is used in a way in which dependencies cannot be statically extracted

Despite the warnings, Edmundo's solution is the most robust because it doesn't depend on Babel.

Solution 4 - Javascript

Extract the call to a separate module:

// src/js/lib/bundle-loader.js
/* istanbul ignore next */
module.exports = require.context('bundle-loader?lazy!../components/', false, /.*\.vue$/)

Use the new module in the module where you extracted it from:

// src/js/lib/loader.js
const loadModule = require('lib/bundle-loader')

Create a mock for the newly created bundle-loader module:

// test/unit/specs/__mocks__/lib/bundle-loader.js
export default () => () => 'foobar'

Use the mock in your test:

// test/unit/specs/lib/loader.spec.js
jest.mock('lib/bundle-loader')
import Loader from 'lib/loader'

describe('lib/loader', () => {
  describe('Loader', () => {
    it('should load', () => {
      const loader = new Loader('[data-module]')
      expect(loader).toBeInstanceOf(Loader)
    })
  })
})

Solution 5 - Javascript

Installing

> babel-plugin-transform-require-context

package and adding the plugin in the .babelrc resolved the issue for me. Refer to the documentation here: https://www.npmjs.com/package/babel-plugin-transform-require-context

Solution 6 - Javascript

Alrighty! I had major issues with this and managed to come to a solution that worked for me by using a combination of other answers and the Docs. (Took me a good day though)

For anyone else who is struggling:

Create a file called bundle-loader.js and add something like:

module.exports = {
  importFiles: () => {
    const r = require.context(<your_path_to_your_files>)
    <your_processing> 
    return <your_processed_files>
  }
}

In your code import like:

import bundleLoader from '<your_relative_Path>/bundle-loader'

Use like

let <your_var_name> = bundleLoader.importFiles()

In your test file right underneath other imports:

jest.mock('../../utils/bundle-loader', () => ({
  importFiles: () => {
    return <this_will_be_what_you_recieve_in_the_test_from_import_files>
  }
}))

Solution 7 - Javascript

The easiest and fastest way to solve this problem will be to install require-context.macro

npm install --save-dev require-context.macro

then just replace:

var modulesReq = require.context('.', false, /\.js$/);

with:

var modulesReq = requireContext('.', false, /\.js$/);

Thats it, you should be good to go! Cheers and good luck!

Solution 8 - Javascript

Implementation problems not mentioned:

  1. Jest prevents out-of-scope variables in mock, like __dirname.
  2. Create React App limits Babel and Jest customization. You need to use src/setupTests.js which is run before every test.
  3. fs is not supported in the browser. You will need something like browserFS. Now your app has file system support, just for dev.
  4. Potential race condition. Export after this import. One of your require.context imports includes that export. I'm sure require takes care of this, but now we are adding a lot of fs work on top of it.
  5. Type checking.

Either #4 or #5 created undefined errors. Type out the imports, no more errors. No more concerns about what can or can't be imported and where.

Motivation for all this? Extensibility. Keeping future modifications limited to one new file. Publishing separate modules is a better approach.

If there's an easier way to import, node would do it. Also this smacks of premature optimization. You end up scrapping everything anyways because you're now using an industry leading platform or utility.

Solution 9 - Javascript

If you're using Jest with test-utils in Vue.

Install these packages:

> @vue/cli-plugin-babel

and

> babel-plugin-transform-require-context

Then define babel.config.js at the root of the project with this configuration:

module.exports = function(api) {
    api.cache(true);

    const presets = [
        '@vue/cli-plugin-babel/preset'
    ];

    const plugins = [];

    if (process.env.NODE_ENV === 'test') {
        plugins.push('transform-require-context');
    }

    return {
        presets,
        plugins
    };
};

This will check if the current process is initiated by Jest and if so, it mocks all the require.context calls.

Solution 10 - Javascript

I faced the same issue with an ejected create-react-app project and no one from the answers above helped me...

My solution were to copy to config/babelTransform.js the follwoing:

module.exports = babelJest.createTransformer({
  presets: [
    [
      require.resolve('babel-preset-react-app'),
      {
        runtime: hasJsxRuntime ? 'automatic' : 'classic',
      },
    ],
  ],
  plugins:["transform-require-context"],
  babelrc: false,
  configFile: false,
});

Solution 11 - Javascript

Simpleset Solution for this

Just Do

var modulesReq = require.context && require.context('.', false, /\.js$/);
if(modulesReq) {
modulesReq.keys().forEach(function(module) {
  modulesReq(module);
});

}

So Here I have added extra check if require.context is defined then only execute By Doing this jest will no longer complain

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionborgesView Question on Stackoverflow
Solution 1 - JavascriptEdmundo RodriguesView Answer on Stackoverflow
Solution 2 - JavascriptSatyam PathakView Answer on Stackoverflow
Solution 3 - JavascriptthompsonsjView Answer on Stackoverflow
Solution 4 - JavascriptborisdiakurView Answer on Stackoverflow
Solution 5 - JavascriptRahul AkuratiView Answer on Stackoverflow
Solution 6 - JavascriptPatrick WardView Answer on Stackoverflow
Solution 7 - JavascriptBartekusView Answer on Stackoverflow
Solution 8 - Javascriptsteve76View Answer on Stackoverflow
Solution 9 - JavascripthatefView Answer on Stackoverflow
Solution 10 - JavascriptmisoloView Answer on Stackoverflow
Solution 11 - JavascriptAditya kumarView Answer on Stackoverflow