Jest的快照测试snapshot
每当你想要确保你的UI不会有意外的改变,快照测试是非常有用的工具。
// app.js
export function app() {
return {
server: 'http://localhost',
port: '8080',
}
}
// 测试文件
test('app快照测试', () => {
expect(app()).toMatchSnapshot();
})
第一次运行此测试案例后,会在同一目录下生成一个__snapshots__的文件夹,里面是app.js文件的快照文件,文件名app.test.js.snap
exports[`app快照测试 1`] = `
Object {
"server": "http://localhost",
"port": "8080",
}
`;
之后运行此测试案例,生成的快照会和之前生成的快照做比对,如果快照一致,则测试通过;如果更改了代码,快照不一致,则测试不通过,此时在终端中按u键即可更新快照,然后测试就会通过了,快照会保存为最新的快照。
如果我们一次性修改了多个快照,那么多个快照测试不会通过,此时按u键,则所有的快照都会更新。但是如果我们想一个个的去检查快照,决定是否更新时,我们需要一个个的更新快照。此时我们按w键,会出现多种快捷键模式,而i键交互式的一个个的更新快照符合我们的需求。



{time: new Date()},每次快照测试时,新快照的time值与老快照的值都不一致,会导致我们的快照测试不通过,那么此时测试案例需要做出修改。
// app.js
export function app() {
return {
server: 'http://localhost',
port: '8080',
time: new Date(),
}
}
// 测试文件
test('app快照测试', () => {
expect(app()).toMatchSnapshot({
time: expect.any(Date)), // 表明time可以是任意时间,Date、String、Number
});
})
如果是要给组件快照测试,推荐react-test-renderer,可以更好的帮助我们对组件进行快照测试。
npm i react-test-renderer -D
// 测试文件
import renderer from 'react-test-renderer';
test('快照测试', ()=> {
const tree = renderer.create(<App />);
expect(tree.toJSON()).toMatchSnapshot();
})
mock异步函数
之前我们讲了可以通过mock axios模块,来实现异步请求的测试,现在我们通过mock异步请求函数来实现异步请求测试。
一个发异步请求的文件
// api.js
export const fetchData = () => {
return axios.get('http://www.dell-lee.com/react/api/demo.json');
}
在与它同级的目录下创建一个文件夹__mocks__,下面再创建一个同样的文件api.js
// __mocks__/api.js
export const fetchData = () => {
return new Promise((resolved, reject) => {
resolved({
data: 123,
})
})
}
这样在测试文件中,jest会自动到__mocks__文件夹下面去找模拟的api.js文件进行测试:
import { fetchData } from './api.js';
jest.mock('./api.js');
test('测试返回值为123', () => {
return fetchData().then(res => {
expect(res.data).toBe(123);
})
})
比如,有时候在api.js文件中有不需要mock的函数getNumber(),此时在测试文件中引入此函数。使用jest.requireActual()这个方法,会从真正的api.js文件中取引入函数。
const { getNumber } = jest.requireActual('./api.js');
mock timers
创建一个timer.js文件,3秒后执行callback函数:
// timer.js
export default (callback) => {
setTimeout(() => {
callback();
}, 3000)
}
// 测试案例
test('测试回调函数', (done) => {
timer(() => {
expect(1).toBe(1);
done();
})
})
测试案例会等3秒后才返回测试结果,如果等待时间太久,是不利于我们做测试的,所以需要添加jest.useFakeTimers(),来帮助我们模拟定时器,用jest.runAllTimers()来快进时间,不必再等待时长。
// 测试案例
jest.useFakeTimers();
test('测试回调函数', () => {
const fn = jest.fn(); // 使用jest来mock一个回调函数
timer(fn);
jest.runAllTimers();
expect(fn).toHaveBeenCalledTimes(1);
})
jest.runAllTimers():快进时间,使所有定时器回调被立刻执行jest.runonlyPendingTimers():只立即执行在等待中的定时器jest.advanceTimersByTime(1000):快进时间至1秒,执行定时器回调
// timer.js
export default (callback) => {
setTimeout(() => {
callback();
setTimeout(() => {
callback();
}, 3000)
}, 3000)
}
// 测试案例
jest.useFakeTimers();
test('测试回调函数', () => {
const fn = jest.fn();
timer(fn);
jest.runAllTimers();
expect(fn).toHaveBeenCalledTimes(2);
})
test('测试回调函数', () => {
const fn = jest.fn();
timer(fn);
jest.runonlyPendingTimers();
expect(fn).toHaveBeenCalledTimes(1);
})
test('测试回调函数', () => {
const fn = jest.fn();
timer(fn);
jest.advanceTimersByTime(3000);
expect(fn).toHaveBeenCalledTimes(1);
jest.advanceTimersByTime(3000);
expect(fn).toHaveBeenCalledTimes(2);
})
Enzyme的配置及使用
Enzyme是Airbnb开源的React测试工具库库,它功能过对官方的测试工具库ReactTestUtils的二次封装,提供了一套简洁强大的 API,并内置Cheerio,实现了jQuery风格的方式进行DOM 处理,开发体验十分友好。在开源社区有超高人气,同时也获得了React 官方的推荐。
安装enzyme,在开发环境
npm i --save-dev enzyme enzyme-adapter-react-16
使用enzyme,在测试文件中引入enzyme
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({ adapter: new Adapter() });
test('', () => {
....
})
enzyme提供了3种测试方法,每个测试方法包含多个API
shallow:浅渲染,是对官方的Shallow Renderer的封装。将组件渲染成虚拟DOM对象,只会渲染第一层,子组件将不会被渲染出来,使得效率非常高。不需要DOM环境, 并可以使用jQuery的方式访问组件的信息。mount:完全渲染,它将组件渲染加载成一个真实的DOM节点,用来测试DOM API的交互和组件的生命周期。用到了jsdom来模拟浏览器环境。render:静态渲染,它将React组件渲染成静态的HTML字符串,然后使用Cheerio这个库解析这段字符串,并返回一个Cheerio的实例对象,可以用来分析组件的html结构。 三种方法中,shallow和mount因为返回的是DOM对象,可以用simulate进行交互模拟,而render方法不可以。一般shallow方法就可以满足需求,如果需要对子组件进行判断,需要使用render,如果需要测试组件的生命周期,需要使用mount方法。
jest+enzyme的使用
// App.js
import React from 'react';
function App(){
return (
<div className='app' title='btn'>hello world</div>
)
}
export default App;
// 测试文件
import App from './App.js';
import Enzyme, { shallow, mount, render } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({ adapter: new Adapter() });
test('测试节点', () => {
const wrapper = shallow(<App />);
expect(wrapper.find('.app').length).toBe(1);
expect(wrapper.find('.app').prop('title')).toBe('btn');
})
在GitHub上enzyme中有关于jest-enzyme的一些新的匹配器,可以更为方便的使用。

安装jest-enzyme:
npm i jest-enzyme --save-dev
// package.json
"jest": {
"setupFilesAfterEnv": ['./node_modules/jest-enzyme/lib/index.js'],
}
那么我们便可以使用新增的匹配器来重新测试上面的案例:
test('测试节点', () => {
const wrapper = shallow(<App />);
expect(wrapper.find('.app')).toExist();
expect(wrapper.find('.app')).toHaveProp('title', 'btn');
})
jest-enzyme中新增的匹配器:

Enzyme的一些API:
type():返回当前组件的类型text():返回当前组件的文本内容html():返回当前组件的HTML代码形式props():返回根组件的所有属性prop(key):返回根组件的指定属性state([key]):返回根组件的状态setState(nextState):设置根组件的状态setProps(nextProps):设置根组件的属性find(selector):返回元素debug():返回当前组件的HTML代码的字符串形式exists([selector]):返回当前组件是否存在的布尔值simulate(event[, ...args]:组件交互
test('测试input change事件', () => {
const wrapper = shallow(<App />);
const inputElem = wrapper.find('input');
inputElem.simulate('change', {
target: { value: 'hello world' },
})
expect(inputElem.prop('value')).toBe('hello world');
})