原文地址: github.com/yinxin630/b…
技术交流: fiora.suisuijiang.com/html
Jest(jestjs.io/) 是由 Facebook 推出的一款优秀的测试框架, 它集成了断言+测试的功能, 无须组合其余工具便可实现单元测试node
首先须要安装 Jest. npm i -D jest
react
建立源码目录 src
, 编写将要测试的方法git
// src/add.js
module.exports = function add(a, b) {
return a + b;
}
复制代码
建立测试目录 __test__
, 编写第一个测试用例github
// __test__/add.test.js
const add = require('../src/add');
test('add()', () => {
expect(add(1, 2)).toBe(3);
});
复制代码
在 package.json 中, 添加 scripts 测试命令, "test": "jest"
web
执行 npm test
运行单元测试, 结果以下 typescript
__test__
是 Jest 默认测试目录, 如需使用其余目录能够在配置文件中修改npm
能够修改 Jest 配置启用覆盖率输出, 在根目录建立配置文件 jest.config.js
, 添加以下内容json
module.exports = {
collectCoverage: true,
}
复制代码
从新执行单元测试, 结果以下 浏览器
同时在你的项目中会生成 coverage
目录, 这里面是 web 版的详细覆盖率报告
咱们先在 package.json 新增一个命令, 来快捷打开 web 版覆盖率报告
添加 "coverage": "open ./coverage/lcov-report/index.html"
执行 npm run coverage
查看报告
首先, 将 add.js
修改成 add.ts
// src/add.ts
export default function add(a: number, b: number) {
return a + b;
}
复制代码
将 add.test.js
修改成 add.test.ts
// __test__/add.test.ts
import add from '../src/add';
test('add()', () => {
expect(add(1, 2)).toBe(3);
});
复制代码
新增 tsconfig.json
添加 TypeScript 配置
// tsconfig.json
{
"compilerOptions": {
"target": "es5",
"strict": true,
},
"include": [
"src/**/*",
"__test__/**/*"
],
"exclude": [
"node_modules",
]
}
复制代码
使用 Jest 测试 TypeScript 代码须要借助 ts-jest
解析器
安装依赖 npm i -D ts-jest typescript @types/jest
修改 Jest 配置文件, 将 ts 文件解析器设置为 ts-jest
// jest.config.js
module.exports = {
collectCoverage: true,
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
}
复制代码
从新执行 npm test
查看结果
首先安装相关依赖
npm i --save react react-dom
npm i -D @types/react @types/react-dom
修改 tsconfig.json
添加 tsx 支持
// tsconfig.json
{
"compilerOptions": {
...
"jsx": "react",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
},
...
}
复制代码
测试 React 代码, 还须要借助 enzyme(airbnb.io/enzyme/), 这是由 Airbnb 出的一个 React 测试工具
安装依赖
npm i -D enzyme enzyme-adapter-react-16 @types/enzyme @types/enzyme-adapter-react-16
新增一个 React 组件 Example
// src/Example.tsx
import React from 'react';
export default function Example() {
return (
<div>Example</div>
)
}
复制代码
新增 Example 组件测试用例
// __test__/Example.test.tsx
import React from 'react';
import { configure, mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import Example from '../src/Example';
configure({ adapter: new Adapter() });
test('<Example>', () => {
const example = mount(<Example/>);
expect(example).toMatchSnapshot({});
expect(example.html()).toBe('<div>Example</div>');
})
复制代码
执行 npm test -- __test__/Example.test.tsx
单独测试 Example 组件, 结果以下
新增一个有 props 的组件 Message
// src/Message.tsx
import React from 'react';
interface MessageProps {
msg: string;
}
export default function Message(props: MessageProps) {
return (
<div>{props.msg}</div>
)
}
复制代码
编写 Message 组件的测试用例
// __test__/Message.test.tsx
...
test('<Message>', () => {
const message = mount(<Message msg="初始消息" />);
expect(message.html()).toBe('<div>初始消息</div>');
// 更新 props
message.setProps({ msg: '更新消息' });
expect(message.html()).toBe('<div>更新消息</div>');
})
复制代码
新增一个监听点击事件的组件 Count
// src/Count.tsx
import React, { useState } from 'react';
export default function Count() {
const [count, setCount] = useState(0);
return (
<div>
<span>{count}</span>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
)
}
复制代码
编写 Count 组件的测试用例
// __test__/Count.test.tsx
...
test('<Count>', () => {
const count = mount(<Count/>);
expect(count.find('span').text()).toBe('0');
// 模拟 click 事件
count.find('button').simulate('click');
expect(count.find('span').text()).toBe('1');
})
复制代码
Jest 默认下是用 jsdom(github.com/jsdom/jsdom) 这个虚拟环境来运行测试的, 它是一个仿浏览器环境, 可是并不支持全部的浏览器 API, 好比 URL.createObjectURL
就是不支持的
对于不支持的 API, 须要咱们对其添加 faker function
// @ts-ignore
global.URL.createObjectURL = jest.fn(() => 'faker createObjectURL');
复制代码
注意, 必定要保证在调用 API 以前就已经注入了 polyfill, 好比某些模块可能包含自执行代码, 在 import 该模块的时候, 就开始调用 API 了, 因此须要将 polyfill 放在 import 以前
仍是以 Count 组件为例, 修改一下逻辑, 再也不点击按钮时自增, 修改成 mounted 1000ms 后自增一次
// src/Count.tsx
import React, { useState, useEffect } from 'react';
export default function Count() {
const [count, setCount] = useState(0);
useEffect(() => {
setTimeout(() => setCount(count + 1), 1000);
}, []);
return (
<div>
<span>{count}</span>
</div>
)
}
复制代码
要测试 setTimeout, 须要使用 Jest 提供的 faker timer
同时还要注意, 使用 faker timer 后, react-dom 要求将更新 state 的逻辑包在 act
方法中, 不然就会出现以下的警告信息
Warning: An update to Count inside a test was not wrapped in act(...).
完整的测试用例以下所示
// __test__/Count.test.tsx
import React from 'react';
import { configure, mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import { act } from 'react-dom/test-utils';
import Count from '../src/Count';
configure({ adapter: new Adapter() });
// 注入 faker timers
jest.useFakeTimers();
test('<Count>', () => {
const count = mount(<Count/>);
expect(count.find('span').text()).toBe('0');
act(() => {
// 等待 1000ms
jest.advanceTimersByTime(1000);
})
expect(count.find('span').text()).toBe('1');
})
复制代码