react生态下jest单元测试
一:jest框架搭建
1.在本地创建一个目录jest_practice
2.使用编辑器VScode打开目录,紧接着在终端中打开,执行npm init
3.执行以下命令:
注意:这里我们使用cnpm去安装速度会更快,npm速度会很慢!
a.建议使用npm install –g jest(不需要单个去安装依赖),修改package.json文件即可。
b.安装jest框架,以及依赖
-
cnpm install --save-dev jest babel-jest babel-core babel-preset-env regenerator-runtime
-
cnpm i --save-dev @babel/plugin-transform-runtime
-
cnpm i --save-dev @babel/preset-env
-
cnpm install @babel/runtime
4.打开package.json文件,修改jest:
"test": "jest --config jest.config.json --no-cache --colors --coverage"
5.搭建好之后需要写个demo来测试是否正确
如上图说明jest框架搭建成功,进入编写case主题
%stmts是语句覆盖率(statement coverage):是不是每个语句都执行了?
%Branch分支覆盖率(branch coverage):是不是每个if代码块都执行了?
%Funcs函数覆盖率(function coverage):是不是每个函数都调用了?
%Lines行覆盖率(line coverage):是不是每一行都执行了?
6.报告配置
需要在module层执行npm install jest-html-reporters --save-dev
新增jest.config.json
-
{
-
"reporters": [
-
"default",
-
["./node_modules/jest-html-reporters", {
-
"publicPath": "./test/html-reports",
-
"filename": "report.htm",
-
"pageTitle": "Test Report",
-
"expand": true
-
}]
-
]
-
}
执行完case会在html-report目录下生成report.html报告
完整报告:
报错详情:
7.执行case方式:
三者都可以,需要安装yarn(cnpm install yarn)
1.npm test //执行全量test.js后缀的文件
2.yarn test --watchALL
3.jest Hook.test.js //执行单个case
二:开工须知
Jest背景:
Jest是 Facebook 发布的一个开源的、基于 Jasmine 框架的 JavaScript单元测试工具。提供了包括内置的测试环境DOM API支持、断言库、Mock库等,还包含了Spapshot Testing、 Instant Feedback等特性。
Enzyme:
React测试类库Enzyme提供了一套简洁强大的API,并通过jQuery风格的方式进行DOM处理,开发体验十分友好。不仅在开源社区有超高人气,同时也获得了React官方的推荐。
1.举例,被测函数:
文件名:Hook.js
-
constructor() {
-
this.init();
-
}
-
-
init() {
-
this.a = 1;
-
this.b = 1;
-
}
-
-
sum() {
-
return this.a + this.b;
-
}
-
}
-
module.exports = Hook;
文件名:Hook.test.js
-
describe('hook', () => {
-
const hook = new Hook();
-
-
// 每个测试用例执行前都会还原数据,所以下面两个测试可以通过。
-
beforeEach(() => {
-
hook.init();
-
});
-
-
test('test hook 1', () => {
-
hook.a = 2;
-
hook.b = 2;
-
expect(hook.sum()).toBe(4);
-
});
-
-
test('test hook 2', () => {
-
expect(hook.sum()).toBe(2);// 测试通过
-
});
-
});
执行此目录下以test.js结尾的case :jest –colors –coverage 结果如下:
执行单个case:jest Hook.test.js –colors –coverage
会在html-report目录下生成report.html文件
2.SnapShot Testing(快照测试):
快照测试第一次运行的时候会将被测试ui组件在不同情况下的渲染结果保存一份快照文件。后面每次再运行快照测试时,都会和第一次的比较,若组件代码有所改变,则快照测试会失败,如果组件代码是最新的,优化过得代码,则需要更新快照,免得每次执行报错。
更新快照命令:jest --updateSnapshot
被测组件代码如下:
-
//被测组件
-
-
import React from 'react';
-
-
const STATUS = {
-
HOVERED: 'hovered',
-
NORMAL: 'normal',
-
};
-
-
export default class Link extends React.Component {
-
constructor() {
-
super();
-
-
this.state = {
-
class: STATUS.NORMAL,
-
};
-
}
-
-
_onMouseEnter = () => {
-
this.setState({class: STATUS.HOVERED});
-
};
-
-
_onMouseLeave = () => {
-
this.setState({class: STATUS.NORMAL});
-
};
-
-
render() {
-
return (
-
<a
-
className={this.state.class}
-
href={this.props.page || '#'}
-
onMouseEnter={this._onMouseEnter}
-
onMouseLeave={this._onMouseLeave}
-
>
-
{this.props.children}
-
</a>
-
);
-
}
-
}
快照测试case:
-
import React from "react";
-
import Link from "./Link.react";
-
import renderer from "react-test-renderer";// react-test-renderer则负责将组件输出成 JSON 对象以方便我们遍历、断言或是进行 snapshot 测试
-
-
//React 组件的 render 结果是一个组件树,并且整个树最终会被解析成一个纯粹由 HTML 元素构成的树形结构
-
-
it("renders correctly", () => {
-
const tree = renderer
-
.create(<Link page="http://www.instagram.com">Instagram</Link>)
-
.toJSON();
-
expect(tree).toMatchSnapshot();
-
});
-
-
it("renders as an anchor when no page is set", () => {
-
const tree = renderer.create(<Link>Facebook</Link>).toJSON();
-
expect(tree).toMatchSnapshot();
-
});
-
-
it("properly escapes quotes", () => {
-
const tree = renderer
-
.create(<Link>{"\"Facebook\" \\'is \\ 'awesome'"}</Link>)
-
.toJSON();
-
expect(tree).toMatchSnapshot();
-
});
-
-
it("changes the class when hovered", () => {
-
const component = renderer.create(
-
<Link page="http://www.facebook.com">Facebook</Link>
-
);
-
let tree = component.toJSON();
-
expect(tree).toMatchSnapshot();
-
-
// manually trigger the callback
-
tree.props.onMouseEnter();
-
// re-rendering
-
tree = component.toJSON();
-
expect(tree).toMatchSnapshot();
-
-
// manually trigger the callback
-
tree.props.onMouseLeave();
-
// re-rendering
-
tree = component.toJSON();
-
expect(tree).toMatchSnapshot();
-
});
-
-
it("renders correctly", () => {
-
const tree = renderer
-
.create(<Link page="https://prettier.io">Prettier</Link>)
-
.toJSON();
-
expect(tree).toMatchInlineSnapshot(`
-
<a
-
className="normal"
-
href="https://prettier.io"
-
onMouseEnter={[Function]}
-
onMouseLeave={[Function]}
-
>
-
Prettier
-
</a>
-
`);
-
});
-
-
//1.通常,在对象中有一些字段需要快照,这些字段是生成的(比如id和Dates)。如果尝试对这些对象进行快照,它们将强制快照在每次运行时失败.
-
//2.Jest允许为任何属性提供非对称匹配器。在写入或测试快照之前,将检查这些匹配器,然后将其保存到快照文件而不是接收到的值
-
-
it('will check the matchers and pass', () => {
-
const user = {
-
createdAt: new Date(),
-
id: Math.floor(Math.random() * 20),
-
name: 'LeBron James',
-
};
-
-
expect(user).toMatchSnapshot({
-
createdAt: expect.any(Date),
-
id: expect.any(Number),
-
});
-
});
//1.通常,在对象中有一些字段需要快照,这些字段是生成的(比如id和Dates)。如果尝试对这些对象进行快照,它们将强制快照在每次运行时失败.//2.Jest允许为任何属性提供非对称匹配器。在写入或测试快照之前,将检查这些匹配器,然后将其保存到快照文件而不是接收到的值
-
it('will check the matchers and pass', () => {
-
const user = {
-
createdAt: new Date(),
-
id: Math.floor(Math.random() * 20),
-
name: 'LeBron James',
-
};
-
-
expect(user).toMatchSnapshot({
-
createdAt: expect.any(Date),
-
id: expect.any(Number),
-
});
-
});
生成快照文件:
快照文件:
-
// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-
exports[`changes the class when hovered 1`] = `
-
<a
-
className="normal"
-
href="http://www.facebook.com"
-
onMouseEnter={[Function]}
-
onMouseLeave={[Function]}
-
>
-
Facebook
-
</a>
-
`;
-
-
exports[`changes the class when hovered 2`] = `
-
<a
-
className="hovered"
-
href="http://www.facebook.com"
-
onMouseEnter={[Function]}
-
onMouseLeave={[Function]}
-
>
-
Facebook
-
</a>
-
`;
-
-
exports[`changes the class when hovered 3`] = `
-
<a
-
className="normal"
-
href="http://www.facebook.com"
-
onMouseEnter={[Function]}
-
onMouseLeave={[Function]}
-
>
-
Facebook
-
</a>
-
`;
-
-
exports[`properly escapes quotes 1`] = `
-
<a
-
className="normal"
-
href="#"
-
onMouseEnter={[Function]}
-
onMouseLeave={[Function]}
-
>
-
"Facebook" \\'is \\ 'awesome'
-
</a>
-
`;
-
-
exports[`renders as an anchor when no page is set 1`] = `
-
<a
-
className="normal"
-
href="#"
-
onMouseEnter={[Function]}
-
onMouseLeave={[Function]}
-
>
-
Facebook
-
</a>
-
`;
-
-
exports[`renders correctly 1`] = `
-
<a
-
className="normal"
-
href="http://www.instagram.com"
-
onMouseEnter={[Function]}
-
onMouseLeave={[Function]}
-
>
-
Instagram
-
</a>
-
`;
-
-
exports[`will check the matchers and pass 1`] = `
-
Object {
-
"createdAt": Any<Date>,
-
"id": Any<Number>,
-
"name": "LeBron James",
-
}
-
`;
更新快照命令:jest --updateSnapshot
3.组件测试
组件代码:example 1
-
'user strict';
-
-
function timeGame(callback){
-
console.log('ready...go!!!');
-
setTimeout(() => {
-
console.log('Time is up ,please stop!!!');
-
callback && callback();
-
}, 1000);
-
}
-
-
module.exports = timeGame;
-
//export default timeGame;
module.exports = timeGame;
//export default timeGame;
组件测试代码:
-
import { jest } from '@jest/globals';
-
import ReactTestUtils from 'react-dom/test-utils';
-
-
'user strict';
-
-
jest.useFakeTimers();
-
-
describe('时间计时器', () => {
-
test('wait 1 second before ending the game', () => {
-
const timeGame = require('./timeGame');
-
timeGame();
-
expect(setTimeout).toHaveBeenCalledTimes(1);
-
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000)
-
});
-
-
test('calls the callback after 1 second ', () => {
-
const timeGame = require('./timeGame');
-
const callback = jest.fn();
-
timeGame(callback);
-
-
expect(callback).not.toBeCalled();
-
jest.runAllTimers();
-
expect(callback).toBeCalled();
-
expect(callback).toHaveBeenCalledTimes(1);
-
});
-
-
-
});
example2:
组件代码:
-
import React from "react";
-
-
class Button extends React.Component {
-
constructor() {
-
super();
-
-
this.state = {
-
disabled: false,
-
};
-
this.handClick = this.handClick.bind(this);
-
}
-
-
handClick() {
-
if (this.state.disabled) {
-
return;
-
}
-
if (this.props.onClick) {
-
this.props.onClick();
-
}
-
this.setState({ disabled: true });
-
setTimeout(() => {
-
this.setState({ disabled: false });
-
}, 200);
-
}
-
-
render() {
-
return (
-
<button className="my-button" onClick={this.handClick}>
-
{this.props.children}
-
</button>
-
);
-
}
-
}
-
-
export default Button;
组件测试代码:
-
import React from 'react';
-
//import TestRenderer from 'react-test-renderer';调用 TestRenderer 的 create 方法并传入要 render 的组件就可以获得一个 TestRenderer 的实例
-
import { jest } from '@jest/globals';
-
import ReactTestUtils from 'react-dom/test-utils';
-
import Button from './Button.jsx';
-
-
-
describe('组件测试', () => {
-
it('测试应该被回调', () => {
-
const onClickMock = jest.fn();
-
const testInstance = ReactTestUtils.renderIntoDocument(
-
<Button onClick={onClickMock}>hello</Button>
-
);
-
const buttonDom = ReactTestUtils.findRenderedDOMComponentWithClass(testInstance, 'my-button');
-
ReactTestUtils.Simulate.click(buttonDom);
-
expect(onClickMock).toHaveBeenCalled();
-
});
-
it('点击之后2秒之内是否可用', () => {
-
const testInstance = ReactTestUtils.renderIntoDocument(<Button>hello</Button>);
-
const buttonDom = ReactTestUtils.findRenderedDOMComponentWithClass(testInstance, 'my-button');
-
ReactTestUtils.Simulate.click(buttonDom);
-
expect(testInstance.state.disabled).toBeTruthy();
-
jest.useFakeTimers();
-
jest.advanceTimersByTime(100);
-
expect(testInstance.state.disabled).toBeTruthy();
-
jest.advanceTimersByTime(201);
-
expect(testInstance.state.disabled).toBeTruthy();
-
});
-
});
4.函数测试
-
//函数:去除空格
-
function removeSpace(s) {
-
if (s == undefined || s == "") {
-
return "";
-
} else {
-
return s.replace(/(^\s*)|(\s*$)/g, "");
-
}
-
}
-
-
export default removeSpace;
函数测试:
-
import removeSpace from './removeSpace';
-
-
//带空格的字符串
-
test('Removal of space', function () {
-
const string = ' camelot.china ';
-
return expect(removeSpace(string)).toBe('camelot.china');
-
-
});
-
-
//字符串string = undefined
-
test('string == undifined', () => {
-
const string = undefined;
-
return expect(removeSpace(string)).toBe("")
-
});
-
-
//字符串string = ""
-
test('string = ""', () => {
-
const string = "";
-
return expect(removeSpace(string)).toBe("")
-
});
5.mock测试
-
//mock_fuction.js
-
-
export default {
-
sum(a , b){
-
return a + b
-
}
-
};
-
import React from 'react';
-
import mock_function from './mock_fuction';
-
import { jest } from '@jest/globals';
-
import { object } from 'prop-types';
-
-
//mock_fuction.test.js
-
-
test('测试jest.fn()', () => {
-
let mockFn = jest.fn();
-
let result = mockFn(1, 2, 3);
-
-
expect(result).toBeUndefined();
-
expect(mockFn).toHaveBeenCalledWith(1, 2, 3);
-
expect(mockFn).toBeCalled();
-
expect(mockFn).toBeCalledTimes(1);
-
});
-
-
test('sum(5, 5) ', () => {
-
expect(mock_function.sum(5, 5)).toBe(10);
-
});
-
-
test('测试jest.fn()返回固定值', () => {
-
let mockFn = jest.fn().mockResolvedValue('default');
-
expect(mockFn).toBe('default');
-
});
-
-
test('测试jest.fn()内部实现', () => {
-
let mockFn = jest.fn((num1, num2) => {
-
return num1 * num2;
-
});
-
expect(mockFn(9, 9)).toBe(100);
-
});
-
-
-
test('测试jest.fn()返回promise', async () => {
-
let mockFn = jest.fn().mockResolvedValue('default');
-
let result = await mockFn();
-
-
expect(result).toBe('default');
-
expect(Object.prototype.toString.call(mockFn())).toBe("[object Promise]")
-
});
文章来源: blog.csdn.net,作者:懿曲折扇情,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.csdn.net/qq_41332844/article/details/126837418
- 点赞
- 收藏
- 关注作者
评论(0)