Jest
到新公司的第一个任务是写个公共的Calendar组件,还挺有意思的。写完之后,作为公共组件,当然要有单测,单侧覆盖率也是越高越好。充分的单测能够降低后续迭代造成bug的几率。
所以来简单记录下使用jest的过程,文本主要讲大致用法,具体细节还是看文档吧
目录结构
----- Component // 你的组件目录
index.tsx // 当前组件源码
...
---- __test__ // 单测相关目录
---- index.test.jsx // 单测编写
---- __snapshots__ // 快照目录
---- index.test.tsx.snap // 快照
关于快照 snapshot
snapshot用于确保组件的UI不会发生意外的更改。snapshot test 用来确保组件有被正确的渲染。通常是先渲染组件,然后用渲染结果跟之前生成的快照文件惊醒比较:
- 如果不匹配,则测试不通过
- 如果主动更新了组件,则要更新快照
简单用例
// 首先引入要测试的组件及其依赖
import React from 'react';
import dayjs from 'dayjs';
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import MockDate from 'mockdate';
import toJson from 'enzyme-to-json';
import Calendar from '../index';
import styles from '../Calendar.styl';
import { throttle } from '../util';
import { CalendarPropsRef } from '../types';
// mock today
// 这里的作用是使用mockdate包,将后边的所有 new Date()的值改为 new Date('2022-05-02')
MockDate.set(new Date('2022-05-02'));
// 为什么要 MockDate?
// 这里是一个很有意思的例子,由于测试的是一个Calendar日历组件,而日历要渲染的日期是跟当前时间相关的
// 即:默认情况下,Calendar会渲染今天所在的月份,同时对于今天,Calendar会有特殊的样式
// 这就会导致,我们的snapshot渲染出的UI,每次都是不一样的,在不同的日期跑单测,snapshot必然比匹配
// 所以,这里通过mockData,把每次测试的 今天 固定在 2022.5.2这一天
// 这样就保证了snapshot不因日期变化而变化,解决了问题
// 测异步用
const delay = (ms = 100) => new Promise((resolve) => setTimeout(resolve, ms));
// describe(name, fn) 用来创建一个block,将多个相关联的test组合在一起
// 一般来说一个组件的所有单测组成一个 describe
describe('Calendar', () => {
// 这里的it其实是test的别名,语义上来说,it should be ... 来表示一个test
// 所以下边写成 test('render correct', () => { 也一样
// it(name, fn, timeout) 分别是 test名称,fn表示测试过程,timeout表示在test终止之前等待多久
// 如果一个test返回了一个Promise,那么test会等Promise resolve之后,才算test完成
// 下边这个是一个典型的 snapshot test
// 用来确保组件UI被正确渲染
it('render correct', () => {
// 渲染一个Calendar组件
const wrapper = mount(<Calendar />);
// 渲染结果toJson之后,跟之前生成的快照文件匹配
expect(toJson(wrapper)).toMatchSnapshot();
});
// 测试Calendar的 className prop 是否生效
it('custom class', () => {
// 渲染代有自定义class的组件
const wrapper = mount(<Calendar className="test" />);
// 看渲染结果中是否存在
wrapper.hasClass('test');
});
// 测试 Calendar组件受控的情况
// 点击日期,从2022.5.1变为2022.5.2
it('isControlled', () => {
// mock一个onChange方法
const onChange = jest.fn();
// 渲染受控的 Calendar组件,包括 value和onChange,初始为2022.5.1
const wrapper = mount(<Calendar value={new Date('2022-05-01')} onChange={onChange} />);
// 通过dom找到 2022.5.2的那天,然后模拟点击事件
wrapper.find(`.${styles.calendarBodyCell}`).at(1).children().at(7).simulate('click');
// 期望 onChange能被调用,并且入参是2022.5.2
expect(onChange).toBeCalledWith(dayjs('2022-05-02').toDate());
});
// 测试ref返回的selectDay方法
it('ref selectDay', () => {
// 渲染Calendar,并且用ref暴露的方法
const ref = React.createRef<CalendarPropsRef>();
mount(<Calendar ref={ref} />);
// selectDay 方法会引起组件状态和ui的变化
// 对于这种操作要包装再 act内部,用来保证后续代码执行的时候,这些变化已经全部完成了
act(() => {
// 选择 2022.5.2这一天
ref.current?.selectDay(new Date('2022-05-02'));
});
// 选完后获取值,判断是否正确
const val = ref.current?.getValue().toDateString();
expect(val).toBe(new Date('2022-05-02').toDateString());
});
// 测试一个throttle方法
it('throttle', async () => {
// 这里手动实现的throttle中使用了 new Date()
// 为了不影响这个测试用例,这里把 new Date() 的行为重置回去
MockDate.reset();
// 写个计数器函数,每次+1
let counter = 0;
const fn = () => counter++;
// 25ms 节流
const fnT = throttle(fn, 25);
// 连续执行其实只执行了一次,只有delay(30)毫秒之后再执行,才会再次生效
// 1
fnT();
fnT();
fnT();
await delay(30);
// 2
fnT();
fnT();
fnT();
await delay(30);
// 3
fnT();
// 所以最后counter === 3
expect(counter).toBe(3);
});
});
版权声明:本文为Creabine原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。