如何在webpack中使用jest?

58
我使用 webpack 来开发 React 组件。以下是它的简单版本:
'use strict';

require('./MyComponent.less');

var React = require('react');

var MyComponent = React.createClass({
  render() {
    return (
      <div className="my-component">
        Hello World
      </div>
    );
  }
});

module.exports = MyComponent;

现在,我想使用jest来测试这个组件。以下是我package.json中相关的部分:

"scripts": {
  "test": "jest"
},
"jest": {
  "rootDir": ".",
  "testDirectoryName": "tests",
  "scriptPreprocessor": "<rootDir>/node_modules/babel-jest",
  "unmockedModulePathPatterns": [
    "react"
  ]
}

当运行npm test时,我遇到了以下错误:

SyntaxError: /Users/mishamoroshko/react-component/src/tests/MyComponent.js:/Users/mishamoroshko/react-component/src/MyComponent.js:/Users/mishamoroshko/react-component/src/MyComponent.less: Unexpected token ILLEGAL

看起来像是webpack需要在jest运行测试之前处理require('./MyComponent.less')
我想知道是否需要使用类似jest-webpack的东西。如果需要,有没有办法指定多个scriptPreprocessor?(请注意,我已经使用了babel-jest

你是否尝试使用Webpack将测试文件编译到单独的目录中,然后运行Jest来检查生成的文件? - badsyntax
我也遇到了这个问题,并且有一个类似的问题在这里 ,概述了我尝试过的方法(也在这里提到),以及它们为什么不足够。 - Matt Derrick
11个回答

26

我找到的忽略必需模块最干净的解决方案是使用moduleNameMapper配置(在最新版本0.9.2上运行)。

这份文档很难理解。我希望下面的内容能够帮助您。

将moduleNameMapper键添加到您的packages.json配置中。项的键应该是所需字符串的正则表达式。以下是使用'.less'文件的示例:

"moduleNameMapper": { "^.*[.](less|LESS)$": "EmptyModule" },
在您的根目录中添加一个 EmptyModule.js 文件:
/**
 * @providesModule EmptyModule
 */
module.exports = '';

由于moduleNameMapper将EmptyModule作为别名用于该模块(点击此处了解providesModule的更多信息), 因此注释很重要。

现在,与正则表达式匹配的每个require引用都将被替换为空字符串。

如果您使用moduleFileExtensions配置与'js'文件一起使用,则确保还将EmptyModule添加到'unmockedModulePathPatterns'中。

这是我最终使用的jest配置:

"jest": {
  "scriptPreprocessor": "<rootDir>/node_modules/babel-jest",
  "moduleFileExtensions": ["js", "json","jsx" ],
  "moduleNameMapper": {
    "^.*[.](jpg|JPG|gif|GIF|png|PNG|less|LESS|css|CSS)$": "EmptyModule"
  },
  "preprocessorIgnorePatterns": [ "/node_modules/" ],
  "unmockedModulePathPatterns": [
    "<rootDir>/node_modules/react",
    "<rootDir>/node_modules/react-dom",
    "<rootDir>/node_modules/react-addons-test-utils",
    "<rootDir>/EmptyModule.js"
  ]
}

确认这个有效,只要确保在注释之前(如另一个注释)EmptyModule.js中没有任何内容。 - mpint
这也是 Facebook 的 create-react-app 所使用的解决方案。稍作修改,但本质上是相同的技术。如果你在一个新的 create-react-app 项目上运行 npm run eject,就可以非常清楚地看到它的实现。 - Don
那个方法可行,但是...这真的是最干净的解决方案吗?如果我们不必创建一个空模块就能告诉Jest忽略某些文件,那就太好了。 - zok
这似乎对我的问题毫无影响。仍然出现相同的“Unexpected token .”错误。 - Aid19801

20

我最终采用了以下方法:

// package.json

"jest": {
  "scriptPreprocessor": "<rootDir>/jest-script-preprocessor",
  ...
}


// jest-script-preprocessor.js
var babelJest = require("babel-jest");

module.exports = {
  process: function(src, filename) {
    return babelJest.process(src, filename)
      .replace(/^require.*\.less.*;$/gm, '');
  }
};

但是,我仍然在想什么才是解决这个问题的正确方法。


我还没有找到更好的解决方案?除非有人知道一个补丁。我感觉做这件事很不舒服 :( - 唉,谢谢你提供的解决方法。 - The Dembinski
不使用代码,modulePathIgnorePatterns 能否达到相同的效果?还有 modulePaths,我能够将其与 webpack 别名一起使用。但是它还需要一个符号链接来匹配别名。这些都是不好的 hack... - Alpar
不知道为什么它对我不起作用,仍然看到相同的错误 :( - Tien Do
jest.moduleNameMapper与模拟文件是正确的方法,文档在此处记录(https://facebook.github.io/jest/docs/webpack.html#content)。 (除非您卡在较旧版本的Jest上) - Samjones

10
我发现使用Jest的moduleNameMapper配置甚至更简单。

// package.json

"jest": {
    "moduleNameMapper": {
      "^.+\\.scss$": "<rootDir>/scripts/mocks/style-mock.js"
    }
}

// style-mock.js

module.exports = {};

更多细节请参阅Jest的教程页面


1
现在这是最好的方法,而且你链接的Jest文档非常清楚地解释了你需要做什么。我已经成功地使用它设置了我的环境,即使我的设置还包括TypeScript。 - Vincent

4

我最近发布了Jestpack,这可能会有所帮助。它首先使用Webpack构建您的测试文件,因此任何自定义模块解析器/加载程序/插件等都可以正常工作,并且您最终得到JavaScript代码。然后,它为Jest提供了一个自定义模块加载器,该加载器能够了解Webpack模块运行时。


3

From Jest docs:

// in terminal, add new dependency: identity-obj-proxy
npm install --save-dev identity-obj-proxy

// package.json (for CSS Modules)
{
  "jest": {
    "moduleNameMapper": {
      "\\.(css|less)$": "identity-obj-proxy"
    }
  }
}

以上代码片段将所有.less文件路由到新的依赖项identity-obj-proxy,当调用时它会返回一个包含类名的字符串,例如对于styles.styleName,它会返回'styleName'


2
我认为一个不那么hacky的解决方案是在你的预处理器中加入条件语句,以匹配JavaScript文件名:
if (filename.match(/\.jsx?$/)) {
    return babelJest.process(src, filename);
} else {
    return '';
}

即使您在 require 行中没有明确设置扩展名,这也可以正常工作,并且不需要对源进行正则表达式替换。

请小心处理 - 我曾经遇到过类似的预处理器,最终得到错误信息 TypeError: React.createClass不是一个函数。由于 return ''; 语句会导致像 node_modules 中的 .js 文件等东西被“丢弃”。很可能这是我的 package.json 的问题 - 可能有一种方式可以忽略这些文件,但我认为重写上面的代码段如下更为谨慎: return babelJest.process(src, filename); } else { return src; //change here }``` - schimmy

1
我曾经遇到过类似的问题,涉及到这种模式。
import React, { PropTypes, Component } from 'react';
import styles from './ContactPage.css';
import withStyles from '../../decorators/withStyles';

@withStyles(styles)
class ContactPage extends Component {

请参考https://github.com/kriasoft/react-starter-kit/blob/9204f2661ebee15dcb0b2feed4ae1d2137a8d213/src/components/ContactPage/ContactPage.js#L4-L7中的示例。

在运行Jest时,我遇到了两个问题:

  • 导入.css文件
  • 应用装饰器@withStylesTypeError: <...> (0 , _appDecoratorsWithStyles2.default)(...)不是一个函数

第一个问题通过在脚本预处理器中对.css进行模拟解决。

第二个问题通过使用unmockedModulePathPatterns来排除装饰器自动模拟解决。

module.exports = {
  process: function (src, filename) {

    ...

    if (filename.match(/\.css$/)) src = '';

    ...

    babel.transform(src, ...
  }
}

基于https://github.com/babel/babel-jest/blob/77a24a71ae2291af64f51a237b2a9146fa38b136/index.js的示例。

还要注意:当你使用jest预处理器时,应该清除缓存:

$ rm node_modules/jest-cli/.haste_cache -r

rm node_modules/jest-cli/.haste_cache -r 这个命令救了我的命,谢谢。 - seanomlor

0
受Misha回答的启发,我创建了一个NPM包来解决这个问题,同时还处理了我遇到的几种情况:

webpack-babel-jest

希望这能为下一个人节省几个小时。

0
我们曾经遇到过与CSS文件类似的问题。就像你之前提到的jest-webpack,它很好地解决了这个问题。您也不必模拟或使用任何模块映射器。我们将npm测试命令从jest替换为jest-webpack,它可以正常工作。

0

如果您正在使用Babel,可以使用类似https://github.com/Shyp/babel-plugin-import-noop的东西在Babel转换期间剥离不需要的导入,并配置您的.babelrctest环境以使用该插件,如下所示:

{
  "env": {
    "development": {
      ...
    },
    "test": {
      "presets": [ ... ],
      "plugins": [
        ["import-noop", {
          "extensions": ["scss", "css"]
        }]
      ]
    }
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接