Jest

基于Jest的单元测试

Jest是由Facebook开源出来的一个测试框架,它集成了断言库、mock、快照测试、覆盖率报告等功能。React官方文档中提及,JestFacebook官方使用的组件测试库。不过React也并不排斥其他测试框架,你也可以根据自己的喜好或者团队的统一选择譬如MochaAVA等测试框架。本部分我们就介绍如何从零开始为项目添加基于Jest的测试用例。

环境搭建

依照惯例,我们先使用create-react-app命令创建项目;在package.json项目文件创建完毕之后即使用npm命令安装所需要的依赖:

npm install --save-dev jest

为了方便在npm中使用jest命令行工具来运行所有的测试用例,我们需要在package.json文件中添加如下脚本配置:

"scripts": {
  "test": "jest"
},

此外,为了方便对我们使用ES2015JSX语法的组件或者类进行测试,我们需要添加部分Babel相关的包体来方便Jest对测试代码进行转化与编译:

$ npm install --save-dev babel-jest babel-preset-es2015 babel-preset-react

依赖安装完毕之后,我们就像配置其他的基于Babel的项目一样,需要添加.babelrc配置文件:

{
  "presets": ["es2015", "react"]
}

环境搭建完毕之后,我们就可以进行简单的测试用例编写了,譬如我们的代码库sum.js文件中有如下简单的相加函数:

export default function sum(a, b) {
  return a + b;
}

该函数对应的测试用例放置在sum.test.js文件中,我们可以参考Maven中的文件目录格式,尽量保持srctest目录下文件结构的一致性。Jest也会为我们自动寻找项目目录下的以.spec.test结尾的文件,或者放置在 __test__ 目录下的文件。我们的测试用例编写如下:

import sum from "../../src/util/sum.js";

test("adds 1 + 2 to equal 3", () => {
  expect(sum(1, 2)).toBe(3);
});

测试代码编写完毕之后,我们使用 jest test/util/sum.test.js 命令来运行测试用例,可以在命令行中得到如下的反馈:

 PASStest/util/sum.test.js
  ✓ adds 1 + 2 to equal 3 (2ms)


Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time:0.565s, estimated 1s
Ran all test suites matching "test/util/sum.test.js".

到这里我们已经搭建了能够支撑JavaScript代码测试的环境,不过往往组件开发中,特别是基于Webpack等打包工具开发时,我们会在组件中导入CSS、图片等静态资源。我们需要配置额外的Mock文件来处理这些静态资源,在package.json文件中添加以jest为键名的配置:

// package.json
{
  "jest": {
    "moduleNameMapper": {
      "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js",
      "\\.(scss|css|less)$": "<rootDir>/__mocks__/styleMock.js"
    }
  }
}

其对应的Mock文件如下:

// __mocks__/styleMock.js
module.exports = {};

// __mocks__/fileMock.js
module.exports = "test-file-stub";

除了对于静态资源文件的配置之外,我们还可以使用类似于Webpack中的 modulesDirectoriesextensions 等配置项来自定义Jest的文件搜索规则:

// package.json
{
  "jest": {
    "modulePaths": ["/shared/vendor/modules"],
    "moduleFileExtensions": ["js", "jsx"],
    "moduleDirectories": ["node_modules", "bower_components", "shared"],
    "moduleNameMapper": {
      "\\.(css|less)$": "<rootDir>/__mocks__/styleMock.js",
      "\\.(gif|ttf|eot|svg)$": "<rootDir>/__mocks__/fileMock.js"
    }
  }
}

Webpack

最后,我们还需要考虑如何在Webpack 2项目中进行Jest配置,其主要关注点在于Webpack 2提供了对于ES2015 Modules的原生支持;而Jest运行于Node环境下,仍然需要将ES2015 Modules转化为CommonJS模块规范。因此我们需要为Webpack 2项目下的.babelrc文件添加test环境的配置:

// .babelrc
{
  "presets": [["es2015", { "modules": false }]],

  "env": {
    "test": {
      "plugins": ["transform-es2015-modules-commonjs"]
    }
  }
}

而如果在组件开发中我们使用了动态模块导入,即 import('some-file.js').then(module => …) 这样的语法,我们还需要添加dynamic-import-node插件:

// .babelrc
{
  "presets": [["es2015", { "modules": false }]],
  "plugins": ["syntax-dynamic-import"],
  "env": {
    "test": {
      "plugins": ["dynamic-import-node"]
    }
  }
}

TypeScript

Jest中支持TypeScript,我们首先需要添加相关的依赖:

$ yarn add -D typescript jest ts-jest @types/jest

我们可以通过自定义preprocessor来进行TypeScript处理:

// package.json
{
  "jest": {
    "moduleFileExtensions": ["ts", "tsx", "js"],
    "transform": {
      "^.+\\.(ts|tsx)$": "<rootDir>/preprocessor.js"
    },
    "testMatch": ["**/__tests__/*.(ts|tsx|js)"]
  }
}
const tsc = require("typescript");

const tsConfig = require("./tsconfig.json");

module.exports = {
  process(src, path) {
    if (path.endsWith(".ts") || path.endsWith(".tsx")) {
      return tsc.transpile(src, tsConfig.compilerOptions, path, []);
    }

    return src;
  }
};

或者直接使用ts-jest

module.exports = {
  transform: {
    "^.+\\.tsx?$": "ts-jest"
  },
  testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
  moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"]
};