【问题标题】:How can I mock Webpack's require.context in Jest?如何在 Jest 中模拟 Webpack 的 require.context?
【发布时间】:2016-11-14 21:14:47
【问题描述】:

假设我有以下模块:

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

Jest 抱怨,因为它不知道 require.context

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

如何模拟它?我尝试使用 setupTestFrameworkScriptFile Jest 配置,但测试看不到我在 require 中所做的任何更改。

【问题讨论】:

  • 你找到答案了吗?

标签: javascript webpack jestjs


【解决方案1】:

我遇到了同样的问题,然后我提出了一个“解决方案”。

我很确定这不是最佳选择。根据此处回答的要点,我最终停止使用它:

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

但是如果你真的需要它,你应该在你调用它的每个文件中包含下面的 polyfill(而不是在测试文件本身,因为 require 在 Node 环境中不会被全局覆盖)。

// 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;
  };
}

使用此函数,您无需更改任何require.context 调用,它将以与它相同的行为执行(如果它在 webpack 上,它将只使用原始实现,如果它在 Jest 内部执行,使用 polyfill 函数)。

【讨论】:

  • 你建议用什么来代替'fs'?我的项目中没有它,当我尝试安装时,我遇到了许多其他错误
  • fs 是 NodeJS 自带的原生包。你不需要安装它。只需直接添加 require('fs') 即可工作,因为您的 Jest 测试在 NodeJS 环境中运行。
  • 不确定我的设置发生了什么,但即使 require()ing 这个 polyfill 在文件中,就在函数内部使用 require.contextthrow()ing 之前看到它是运行时,require.context 始终未定义。
  • 综合考虑,这对于个人项目以外的任何事情都不是一个好的解决方案。请考虑在企业应用中使用 thompsonsj 提供的解决方案。
【解决方案2】:

将调用提取到单独的模块:

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

在您从中提取新模块的模块中使用新模块:

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

为新创建的 bundle-loader 模块创建一个 mock:

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

在您的测试中使用模拟:

// 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)
    })
  })
})

【讨论】:

  • @Bartekus 如果您说这不是一个好的解决方案(或否决),也请您解释一下,为什么您认为这不是一个好的解决方案。
  • @borisdiakur 确实,你是对的,但在第二个问题上,尽管你的解决方案不一定是我在企业级软件中使用的,但它并不是不正确的。因此,我实际上是通过一起投票来对社区造成伤害,因为我会在我的个人项目或使用 webpack 但不同时使用 Babel 的企业软件中使用它(这种类型当然不是不可能的)发生的情景)。话虽如此,我的答案已锁定,但如果您对其进行编辑(即使您不更改内容),我将删除我的反对票。
【解决方案3】:

如果你正在使用 Babel,请查看 babel-plugin-require-context-hook。 Storybook 的配置说明可在 Storyshots | Configure Jest to work with Webpack's require.context() 获得,但它们不是 Storyshots/Storybook 特定的。

总结一下:

安装插件。

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

创建一个文件.jest/register-context.js,内容如下:

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

配置 Jest(该文件取决于您存储 Jest 配置的位置,例如package.json):

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

将插件添加到.babelrc

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

或者,将其添加到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
  }
}

值得注意的是,使用babel.config.js 而不是.babelrc 可能会导致问题。比如我在babel.config.js中定义require-context-hook插件时发现:

  • Jest 22 没有接听;
  • Jest 23 捡到了;但是
  • jest --coverage 没接(也许伊斯坦布尔没有跟上 Babel 7 的速度?)。

在所有情况下,.babelrc 配置都可以。


关于Edmundo Rodrigues's answer的备注

这个babel-plugin-require-context-hook 插件使用的代码类似于 Edmundo Rodrigues 在此处的回答。埃德蒙多的道具!因为该插件是作为 Babel 插件实现的,所以它避免了静态分析问题。例如使用 Edmundo 的解决方案,Webpack 警告:

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

尽管有警告,但 Edmundo 的解决方案是最强大的,因为它不依赖 Babel。

【讨论】:

  • 我在 Storybook 上看到了这些说明,按照它们并得到一个新错误,我的 JSX 标签无法识别...(0, _react2.storiesOf)('.../...', module).add('...', () =&gt; &lt;_common.Card&gt; 我得到的错误是...SyntaxError: Unexpected token &lt; where @ 987654344@是一个自定义的react组件,也用jsx div测试过
  • 请注意babel.config.js的问题已经不存在了,这里.babelrc的使用方式,也可以用在babel.config.js中。谢谢@thompsonsj
  • 请将process.env.ENV_NODE改为process.env.NODE_ENV
【解决方案4】:

安装

babel-plugin-transform-require-context

打包并在 .babelrc 中添加插件为我解决了这个问题。 请参阅此处的文档: https://www.npmjs.com/package/babel-plugin-transform-require-context

【讨论】:

    【解决方案5】:

    对此的简单解决方案

    做事

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

    }

    所以我在这里添加了额外的检查,如果定义了 require.context 然后只执行 By Doing 这个玩笑将不再抱怨

    【讨论】:

    • 是的,但是您也会丢失 Jest 中的导入,并且通过扩展失去测试与这些模块相关的任何内容的能力。
    【解决方案6】:

    好的!我对此有重大问题,并通过结合使用其他答案和文档来设法找到对我有用的解决方案。 (不过让我度过了美好的一天)

    对于其他正在苦苦挣扎的人:

    创建一个名为 bundle-loader.js 的文件并添加如下内容:

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

    在您的代码中导入如下:

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

    使用喜欢

    let <your_var_name> = bundleLoader.importFiles()
    

    在其他导入下方的测试文件中:

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

    【讨论】:

    • 感谢您发布您的解决方案,没有其他方法对我有用。
    【解决方案7】:

    花了几个小时尝试上述每个答案。我愿意贡献。

    babel-plugin-transform-require-context 插件添加到.babelrc 用于测试环境解决了所有问题。

    安装 - babel-plugin-transform-require-context 此处为 https://www.npmjs.com/package/babel-plugin-transform-require-contextyarn 也可用)

    现在将插件添加到.babelrc

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

    它将简单地将用于测试环境的require-context 转换为虚拟的fn 调用,以便代码可以安全运行。

    【讨论】:

    • 插件名称应为transform-require-context
    • 正确@Yael 感谢您的通知,我错过了更新
    • 未来读者请注意:这并不能解决问题本身,它只是通过解决空上下文来解决问题。如果您的代码仍然需要 require.context 才能工作 - 这个答案对您没有帮助。
    • 这实际上适合我的需要。谢谢。
    • 你真的救了我。此外,如果您的项目不包含 .babelrc,您可以在项目根文件夹中 babel.config.js 的导出配置数组中添加配置。再次感谢您
    【解决方案8】:

    未提及的实施问题:

    1. Jest 可防止 mock 中的超出范围的变量,例如 __dirname
    2. 创建 React 应用程序限制 Babel 和 Jest 自定义。您需要使用在每次测试之前运行的src/setupTests.js
    3. 浏览器不支持fs。您将需要类似 browserFS 的东西。现在您的应用具有文件系统支持,仅供开发人员使用。
    4. 潜在的竞争条件。此导入后导出。您的require.context 进口之一包括该出口。我确信 require 会解决这个问题,但现在我们在此基础上添加了很多 fs 工作。
    5. 类型检查。

    #4 或#5 都创建了未定义的错误。输入导入,没有更多错误。无需再担心什么可以或不能导入以及在哪里导入。

    这一切的动机?可扩展性。将未来的修改限制在一个新文件中。发布单独的模块是更好的方法。

    如果有更简单的导入方法,node 会这样做。这也带有过早优化的味道。无论如何,您最终都会报废所有内容,因为您现在使用的是行业领先的平台或实用程序。

    【讨论】:

      【解决方案9】:

      解决这个问题最简单、最快的方法是安装require-context.macro

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

      然后只需替换:

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

      与:

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

      就是这样,你应该很高兴! 干杯,祝你好运!

      【讨论】:

        【解决方案10】:

        如果您在 Vue 中使用 Jesttest-utils

        安装这些包:

        @vue/cli-plugin-babel

        babel-plugin-transform-require-context

        然后用这个配置在项目的根目录下定义babel.config.js

        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
            };
        };
        

        这将检查当前进程是否由 Jest 启动,如果是,它会模拟所有 require.context 调用。

        【讨论】:

          【解决方案11】:

          我在弹出的 create-react-app 项目中遇到了同样的问题 上面的答案中没有人帮助我......

          我的解决方案是将以下内容复制到config/babelTransform.js

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

          【讨论】:

            猜你喜欢
            • 2020-10-02
            • 2021-06-09
            • 2017-07-07
            • 2021-11-02
            • 2019-12-26
            • 2021-08-20
            • 2018-11-18
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多