技术交换群: https://fiora.suisuijiang.com/
原文链接: https://github.com/yinxin630/...

Jest 是一款简略, 容易上手且性能非常弱小的测试框架

装置

yarn add -D jest

应用

创立 test 目录, 增加 plus.spec.js 文件

describe('example', () => {    it('should equal 2', () => {        expect(1 + 1).toBe(2);    });});

执行 yarn jest 或者 yarn jest test/plus.spec.js 运行测试用例

胜利后果

失败后果

输入测试覆盖率

在根目录创立 jest.config.js 配置文件

module.exports = {    collectCoverage: true,};

创立 plus.js 模块

module.exports = function plus(a, b) {    return a + b;}

批改测试用例应用模块

const plus = require('../plus');describe('example', () => {    it('should equal 2', () => {        expect(plus(1, 1)).toBe(2);    });});

再次执行测试, 输入覆盖率如下

在浏览器中关上 coverage/lcov-report/index.html 能够浏览覆盖率后果页面

疏忽局部文件或者代码行的覆盖率

批改 plus.ts 模块, 增加更多分支

export default function plus(a: number, b: number) {    if (a + b > 100) {        return 0;    } else if (a + b < 0) {        return 0;    } else {        return a + b;    }}

从新执行测试, 覆盖率输入后果

你能够欠缺测试用例, 或者可能有些文件(譬如 config)和代码分支并不需要测试, 能够将其在测试覆盖率后果中排除, 参考如下配置

  1. 疏忽目录下所有文件

jest.config.js 中增加

collectCoverageFrom: [    '**/*.{ts,tsx}',    '!**/node_modules/**',    '!**/[directory path]/**',],

! 结尾的示意疏忽与其匹配的文件

  1. 疏忽单个文件

在该文件顶部增加 /* istanbul ignore file */

  1. 疏忽一个函数, 一块分支逻辑或者一行代码

在该函数, 分支逻辑或者代码行的上一行增加 /* istanbul ignore next */

反对 Typescript

执行 yarn add -D typescript ts-jest @types/jest 装置 typescript 和申明
并在 jest.config.js 中增加 preset: 'ts-jest'

plus.js 重命名为 plus.ts

export default function plus(a: number, b: number) {    return a + b;}

同样的, 将 plus.spec.js 重命名为 plus.spec.ts

import plus from '../plus'describe('example', () => {    it('should equal 2', () => {        expect(plus(1, 1)).toBe(2);    });});

执行测试, 后果和之前统一

执行单测时不校验 ts 类型

有时你可能会心愿不校验 ts 类型, 仅执行代码测试, 比方须要在 CI 中将类型校验和单元测试分为两个工作
jest.config.js 中增加如下内容

globals: {    'ts-jest': {        isolatedModules: true,    },}

测试 React 组件

装置 react 依赖 yarn add react react-dom 和申明 yarn add -D @types/react
装置 react 测试库 yarn add -D @testing-library/react @testing-library/jest-dom

增加 typescript 配置文件 tsconfig.json

{    "compilerOptions": {        "target": "es2018",        "strict": true,        "moduleResolution": "node",        "jsx": "react",        "allowSyntheticDefaultImports": true,        "esModuleInterop": true,        "lib": ["es2015", "es2016", "es2017", "dom"]    },    "exclude": ["node_modules"]}

新增测试组件 Title.tsx

import React from 'react';function Title() {    return (        <h1>Title</h1>    );}export default Title;

新增测试用例 test/Title.spec.tsx

/** * @jest-environment jsdom */import React from 'react';import { render } from '@testing-library/react';import '@testing-library/jest-dom/extend-expect';import Title from '../Title';describe('Title', () => {    it('should render without error', () => {        const { getByText } = render(<Title />);        const $title = getByText('Title');        expect($title).toBeInTheDocument();    });});

执行 yarn jest test/Title.spec.ts 查看后果

解决动态资源援用

react 组件有时援用一些动态资源, 譬如图片或者 css 样式表, webpack 会正确的解决这些资源, 然而对 Jest 来讲, 这些资源是无奈辨认的

创立 Title.less 样式表

h1 {    color: red;}

批改 Ttitle.tsx, 增加款式援用 import './Title.less';

执行测试会报错

咱们须要配置 transform 对其解决

在根目录创立 jest.transformer.js

const path = require('path');module.exports = {    process(src, filename) {        return `module.exports = ${JSON.stringify(path.basename(filename))};`;    },};

这里是将资源文件名作为模块导出内容

批改 jest.config.js 增加如下配置

transform: {    '\\.(less)$': '<rootDir>/jest.transformer.js', // 正则匹配, 解决 less 款式},

而后从新执行测试就能够了

解决 css in js

如果你应用了相似 linaria 这种 css in js 计划, 其中的 css 款式模板字符串是不反对运行时编译的

批改 Title.tsx

import React from 'react';import { css } from 'linaria';const title = css`    color: red;`;function Title() {    return <h1 className={title}>Title</h1>;}export default Title;

运行测试会报错

linaria 是通过 babel 插件将其预编译为 class 名的, 这里能够 mock 一下 css 函数, 返回一个随机值作为 class 名

在根目录创立 jest.setup.js

jest.mock('linaria', () => ({    css: jest.fn(() => Math.floor(Math.random() * (10 ** 9)).toString(36)),}));

批改 jest.config.js 增加如下配置

setupFilesAfterEnv: ['./jest.setup.js'],

从新执行测试就能够了

测试交互事件

新增 Count.tsx 组件

import React, { useState } from 'react';function Count() {    const [count, updateCount] = useState(0);    return (        <div>            <span data-testid="count">{count}</span>            <button data-testid="button" onClick={() => updateCount(count + 1)}>                +1            </button>        </div>    );}export default Count;

新增 test/Count.spec.tsx 组件

/** * @jest-environment jsdom */import React from 'react';import { render, fireEvent } from '@testing-library/react';import '@testing-library/jest-dom/extend-expect';import Count from '../Count';describe('Count', () => {    it('should render without error', () => {        const { getByTestId } = render(<Count />);        const $count = getByTestId('count');        const $button = getByTestId('button');        expect($count).toHaveTextContent('0');        fireEvent.click($button);        expect($count).toHaveTextContent('1');    });});

这里通过 testId 来查找元素, 应用 fireEvent 触发 click 事件

测试函数调用

新增 Button.tsx 组件

import React from 'react';type Props = {    onClick: () => void;};function Button({ onClick }: Props) {    return <button onClick={onClick}>button</button>;}export default Button;

增加 test/Button.spec.tsx 测试用例

/** * @jest-environment jsdom */import React from 'react';import { render, fireEvent } from '@testing-library/react';import '@testing-library/jest-dom/extend-expect';import Button from '../Button';describe('Button', () => {    it('should render without error', () => {        const handleClick = jest.fn(); // mock 函数        const { getByText } = render(<Button onClick={handleClick} />); // 传递 props        const $button = getByText('button');        fireEvent.click($button);        expect(handleClick).toHaveBeenCalled(); // 冀望其被调用    });});

测试蕴含定时器的逻辑

// timer.tslet cache = 'cache';export default function timer() {    setTimeout(() => {        cache = '';    }, 1000);    return cache;}
// test/timer.spec.tsimport timer from '../timer'jest.useFakeTimers(); // 代替原生计时器describe('timer', () => {    it('should clear cache after timer out', () => {        expect(timer()).toBe('cache');        jest.advanceTimersByTime(1000); // 让计时器后退 1000ms        expect(timer()).toBe('');    })})

mock 依赖模块

要测试的模块可能依赖于其余模块或者第三方 npm 包的后果, 咱们能够应用 Mock Functions 对其进行 mock

// test/mock.spec.tsimport { mocked } from 'ts-jest/utils';import plus from '../plus';jest.mock('../plus');describe('mock', () => {    it('should return mock value', () => {        mocked(plus).   (50);        expect(plus(1, 1)).toBe(50);    });});

还有官网 mock axios npm 模块的例子 https://jestjs.io/docs/en/moc...

mock 环境变量和命令行参数

有的模块会从环境变量和命令行参数取值, 并且可能是在模块初始化时获取的

// process.tsconst { env, argv } = process;export function getEnvironmentValue() {    return env.Value;}export function getProcessArgsValues() {    return argv[2];}

这种状况咱们须要在每个测试用例中, 应用动静 require 来运行时引入改模块, 并且设置其每次引入时删除 cache

// test/process.spec.tsdescribe('mock process', () => {    beforeEach(() => {        jest.resetModules();    });    it('should return environment value', () => {        process.env = {            Value: 'value',        };        const { getEnvironmentValue } = require('../process');        expect(getEnvironmentValue()).toBe('value');    });    it('should return process args value', () => {        process.argv = ['value'];        const { getProcessArgsValues } = require('../process');        expect(getProcessArgsValues()).toBe('value');    });});