Typescript + Mocha + Chai 测试方案

测试框架和断言库选用 Mocha + Chai,能够比较好对支持typescript。
下面结合实际项目,同时参考了其他文章,提炼了一个案例。

安装依赖

首先安装 mocha chai ts-node 以及对应的声明文件 @types/chai @types/mochats-node 不需要安装声明文件,它不会被引入我们的代码文件中。

$ tnpm install chai mocha ts-node @types/chai @types/mocha --save-dev

ts-node 提供了node下的typescript执行环境,无需编译ts文件到,直接运行,方便我们开发。 也可以嵌入vscode等编辑器中进行代码调试,具体可以找其他教程了解。

编写测试文件

首先我们编写简单的测试文件:

src/app/hello-world.ts
export const hello = () => "Hello world!";

然后编写一个测试用例:

src/app/hello-world.spec.ts
import { hello } from './hello-world';
import { expect } from 'chai';
import 'mocha';

describe('测试用例', () => {

  it('返回 hello world', () => {
    const result = hello();
    expect(result).to.equal('Hello world!');
  });

});

运行单元测试

我们在 package.json 中加入 test 命令来运行我们的测试:

package.json
{
   "scripts": {
        "test": "mocha -r ts-node/register src/**/*.spec.ts"
   }
}

在命令行执行 npm run test

问题一:如何忽略样式文件

如果我们的项目不是一个node项目中,一般都会在项目中引入了样式文件,例如:

src/app/style.less
html, body {
  height: 100%;
}
src/app/hello-world.ts
import './style.less';

export const hello = () => "Hello world!";

但是在ts-node中,样式文件是无法识别的,并且导致一些报错。

我们需要引入一个新的插件 ignore-styles

$ tnpm install ignore-styles --save-dev

然后在 package.json 中加入相应的参数:

package.json
{
   "scripts": {
        "test": "mocha -r ts-node/register  -r ignore-styles  src/**/*.spec.ts"
   }
}

问题二:如何使用DOM

ts-node 是跑node下的ts运行环境,并没有浏览器中的DOM、window之类的对象。
如果代码中引用了相应的DOM对象,直接跑ts-node 会报错。

node下可以利用karma等测试服务框架来搭建浏览器的运行环境,也可以用 js-dom 之类等插件提供浏览器的运行环境。

这里以 js-dom 为例:

$ tnpm install jsdom jsdom-global --save-dev

package.json 中加入相应的参数:

package.json
{
   "scripts": {
        "test": "mocha -r ts-node/register  -r ignore-styles  -r jsdom-global/register src/**/*.spec.ts"
   }
}

问题三:如何在使用绝对目录后保证测试正常

通常我们在项目中引入文件,使用的都是相对目录。不过有时候,我们会遇到层级特别深的文件引用情况,此时就可能出现 ../../../../ 这种冗长的引用:

src/app/demo1/demo2/demo3/demo4/hello-code.ts
import hello from '../../../../hello-world';

export const hello2 = () => {
    return hello()
};

typescript 支持使用绝对路径,首先更新 tsconfig.json 文件,修改 compilerOptions 中的 baseUrl 属性和 paths 属性:

tsconfig.json
{
    "compilerOptions": {
        "module": "commonjs",
        "moduleResolution": "node",
        "jsx": "react",
        "baseUrl": "./",
        "paths": {
            "app/*": [ "src/app/*" ],
        }
    }
}

修改完后,我们就可以在代码文件中使用绝对路径了。

src/app/hello-code.ts
import hello from 'app/hello-world';

export const hello2 = () => {
    return hello()
};

这里顺便提一下,如果使用了 Webpack 来打包文件,也需要配置 webpack.config.js 的路径别名:

webpack.config.js
const path = require('path');

function srcPath(subdir) {
    return path.join(__dirname, "src", subdir);
}
module.exports = {
    resolve: {
        alias: {
            app: srcPath('app'),
        },
        // ...
    },
    // ...
};

上面用到来绝对路径,但是 ts-node 暂时不支持解析绝对路径,运行的时候会报错。
这里需要引入一个路径解析的插件:

$ tnpm install --save-dev tsconfig-paths

package.json 中加入相应的参数:

package.json
{
   "scripts": {
        "test": "mocha -r ts-node/register  -r ignore-styles  -r jsdom-global/register -r tsconfig-paths/register src/**/*.spec.ts"
   }
}

问题四:解决使用webpack后,module设置为ES6导致测试模块导入异常的问题

我们利用 Webpack 进行代码分割和打包,为了让ts文件更好的支持 Webpack 的打包策略,我们需要对 tsconfig.json 文件稍稍进行修改:

tsconfig.json文件
{
  "compilerOptions": {
    "outDir": "./dist/",
    "target": "es5",
    "module": "es6",
    "moduleResolution": "node",
    "sourceMap": true,
    "experimentalDecorators": true,
    "lib": [ "dom", "es6", "es5" ]
  },
  "include": [
    "./src/**/*"
  ],
  "exclude": [
    "./src/**/*.spec.ts"
  ],
  "compileOnSave": false
}

上面最重要的修改部分是 modulemoduleResolution,之前我们通常会把 module 配置为 commonjs
这在 Node下是没有问题的,但是为了配合 Webpack 的代码分割功能,我们需要将 module 属性设置为 es6 或者 esnext,但是这种修改又会导致 mochats-node下无法正常工作。
这里有两种解决思路:

思路一,为测试目录单独配置 tsconfig.json,将 module 属性设置为 commonjs;
思路二,运行测试命令的时候动态修改tsconfig.jsonmodule 的值;

这里介绍一下第二种操作:
我们利用 cross-env 来抹平不同系统间命令行的差异:

$ tnpm install cross-env --save-dev

接着在 package.json 中配置:

{
  "scripts": {
    "test": "cross-env TS_NODE_COMPILER_OPTIONS='{ \"module\": \"commonjs\" }' mocha -r ts-node/register -r ignore-styles -r jsdom-global/register -r tsconfig-paths/register src/**/*.spec.ts"
  }
}

我们利用 TS_NODE_COMPILER_OPTIONS来设置一个环境变量动态的修改 tsconfig.json中的 module 属性。

最后执行 npm run test,测试文件就可以正常的跑了。