快照测试在你要确保你的UI没有发生改变的时候非常有用。jest的快照测试为文本测试,第一次执行时存储本次的快照,然后在之后的测试过程中进行文本比对。
import React from 'react';
import Link from '../Link.react';
import renderer from 'react-test-renderer';it('renders correctly', () => {const tree = renderer.create(<Link page="http://www.facebook.com">Facebook</Link>).toJSON();expect(tree).toMatchSnapshot();
});
The snapshot artifact should be committed alongside code changes, and reviewed as part of your code review process.
注意生成的快照文件应该和代码的变更一起被提交和Review。
有时候快照测试不通过是因为组件更新了,那么快照如何更新呢?
(1)你可以通过jest --updateSnapshot or jest -u 方法来让jest为所有快照测试失败的组件重新生成快照。如果你想精确控制哪些快照被更新,可以用–testNamePattern参数指定正则表达式。
(2) 使用watch模式来更新 jest --watch
按i可以交互式的更新失败的快照。
更多请参考:
https://jestjs.io/docs/en/snapshot-testing
(1) 使用jest.fn()函数生成模拟函数
const onDropdownVisibleChange = jest.fn();
(2) 在组件的回调中指定该模拟函数为回调函数
const wrapper = mount(<Select open onDropdownVisibleChange={onDropdownVisibleChange}><Option value="1">1</Option></Select>,
);
(3-1) 模拟点击后进行断言,toHaveBeenLastCalledWith断言被特定参数调用
wrapper.find('.ant-select').simulate('click');
expect(onDropdownVisibleChange).toHaveBeenLastCalledWith(false);
完整实例:
const onDropdownVisibleChange = jest.fn();
const wrapper = mount(<Select open onDropdownVisibleChange={onDropdownVisibleChange}><Option value="1">1</Option>
</Select>,
);
wrapper.find('.ant-select').simulate('click');
expect(onDropdownVisibleChange).toHaveBeenLastCalledWith(false);
(3-2) 模拟点击后进行断言,toHaveBeenCalled断言被调用
const onDropdownVisibleChange = jest.fn();
const wrapper = mount(<Select open onDropdownVisibleChange={onDropdownVisibleChange}><Option value="1">1</Option>
</Select>,
);
wrapper.find('.ant-select').simulate('click');
expect(onDropdownVisibleChange).toHaveBeenCalled();
通常一些本地的时间方法比如setTimeout等不太适合测试环境,因为这些方法会依赖真实的时间流逝。jest可以交换这些时间函数,控制时间的推移,比方说:
beforeAll(() => {jest.useFakeTimers();
});// 或者有多个测试用例使用在每个测试用例执行之前执行
beforeEach(() => {jest.useFakeTimers();
});
还有一些测试用例用于测试某个回调一秒后会被调用:
test('calls the callback after 1 second', () => {const timerGame = require('../timerGame');const callback = jest.fn();timerGame(callback);// 这个时间点还没有被调用expect(callback).not.toBeCalled();// 快进,让所有时间回调都执行jest.runAllTimers();// 现在回调被调用expect(callback).toBeCalled();expect(callback).toHaveBeenCalledTimes(1);
});
在测试过程中可能想要恢复时间正常流逝,可以使用useRealTimers()方法恢复
更多关于时间控制的方法关注:
Timer Mocks · Jest
wrapper.find(‘a’).simulate(‘click’);
如:
class Foo extends React.Component {constructor(props) {super(props);this.state = { count: 0 };}render() {const { count } = this.state;return (<div><div className={`clicks-${count}`}>{count} clicks</div><a href="url" onClick={() => { this.setState({ count: count + 1 }); }}>Increment</a></div>);}
}const wrapper = shallow(<Foo />);expect(wrapper.find('.clicks-0').length).to.equal(1);
wrapper.find('a').simulate('click');
expect(wrapper.find('.clicks-1').length).to.equal(1);
wrapper.find('.ant-input').simulate('change', { target: { value: 'test' } });
wrapper.find('.ant-checkbox-input').at(0).simulate('change', { target: { checked: true } });
比方说antd的一个测试用例是检测用户如果全量引入antd的包,而不是动态引入组件的话,那么会有个提示,怎么验证这个提示被成功展示了呢?
(1) 监控对应的方法,如这里是console.warn方法,但是不让他真的输出出来。
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
(2)使用断言toBeCalledWith,被用xxx参数调用
expect(warnSpy).toBeCalledWith('You are using a whole package of antd, please use https://www.npmjs.com/package/babel-plugin-import to reduce app bundle size.',);
(3) 恢复
warnSpy.mockRestore();
更多参考:https://jestjs.io/docs/jest-object
expect(dropdownWrapper.props().visible).toBe(true);
wrapper.setProps({ open: false });
expect(wrapper.instance().state.status).toBe(0);
expect(wrapper.instance().state.placeholderStyle).toBe(undefined);
如果说组件代码中使用了debounce方法,比方说经常我们需要用debounce去控制请求的发送频率:
import debounce from 'lodash/debounce';
//...
this.fetchData = debounce(this.fetchUserData, this.props.delayTime);
但是在测试用例中怎么去让debounce返回的函数被立即执行呢?毕竟原函数默认是需要delay一段时间后再执行的,答案是可以使用jest的mock方法。
import debounce from 'lodash/debounce';
jest.mock('lodash/debounce');
这样 debounce 方法中就会被加上 mockImplementation 以及 mockRestore 方法
beforeEach(() => {jest.useFakeTimers();debounce.mockImplementation(fn => fn);
});afterEach(() => {jest.useRealTimers();debounce.mockRestore();
});
比方说在 beforeEach 和 afterEach 中分别调用 mockImplementation 和 mockRestore , fn => fn 的含义是指返回这个函数本身,比方说:
this.fetchData = debounce(this.fetchUserData, this.props.delayTime);
运行完debounce函数后, this.fetchData === this.fetchUserData ,那么调用 this.fetchData 函数的时候请求就会立即执行了。
一个完整的测试用例的案例参考如下:
import * as React from 'react';
// eslint-disable-next-line import/no-extraneous-dependencies
import { mount } from 'enzyme';
import debounce from 'lodash/debounce';
import UserSearch from '../index';jest.mock('lodash/debounce');describe('<UserSearch />', () => {beforeEach(() => {jest.useFakeTimers();debounce.mockImplementation(fn => fn);});afterEach(() => {jest.useRealTimers();debounce.mockRestore();});it('should stop fetching if users empty', done => {const wrapper = mount(<UserSearch userSearchRequest={() => Promise.resolve([])} />,);wrapper.find('input').simulate('change', { target: { value: '1' } });jest.runAllTimers();process.nextTick(() => {wrapper.update();expect(wrapper.instance().state.fetching).toBe(false);done();});});});
异步方法通常都具有不确定性,promise的方法也是,但是我们希望在函数调用后立即能够看到组件的执行效果,怎么办呢?答案是使用 process.nextTick() 方法。
比方说我的promise方法是这样:
fetchData = query => {const { userSearchRequest } = this.props;userSearchRequest &&userSearchRequest({ query }).then(users => {...dataSource = ...;this._mounted && this.setState({ dataSource, fetching: false });}).catch(err => {this._mounted && this.setState({ fetching: false });});};
在promise完成之后setState,我希望去测试setState之后的效果,相应的测试用例如下:
it('should stop fetching if users empty', done => {const wrapper = mount(<UserSearch userSearchRequest={() => Promise.resolve([])} />,);wrapper.find('input').simulate('change', { target: { value: '1' } });jest.runAllTimers();process.nextTick(() => {wrapper.update();expect(wrapper.instance().state.fetching).toBe(false);done();});});
nextTick 会在目前队列中的所有事件完成之后执行,保证了promise方法此时已经完成执行。
在我们的测试当中涉及日期组件的时候,进行snapshot测试会出现snapshot总是不通过的问题,那是因为moment总是获取当前的日期,造成snapshot的变化,为了解决这个问题,我们可以mock日期,让他返回固定的日期。
我们使用 mockdate 来帮我们mock 日期相关的方法
tnpm install --save-dev mockdate
然后创建mock方法:
import moment from 'moment';
import MockDate from 'mockdate';export function setMockDate(dateString = '2017-09-18T03:30:07.795') {MockDate.set(moment(dateString));
}export function resetMockDate() {MockDate.reset();
}
最后在测试开始之前调用mock方法即可:
import React from 'react';
import { mount } from 'enzyme';
import toJson from 'enzyme-to-json';
import DateRanger from '..';
import { setMockDate, resetMockDate } from './utils';describe('DateRanger', () => {beforeEach(() => {setMockDate();});afterEach(() => {resetMockDate();});it('should match snapshot', () => {const wrapper = mount(<DateRanger />);// 快照一致expect(toJson(wrapper)).toMatchSnapshot();wrapper.unmount();});
});
expect(dropdownWrapper.find('MenuItem').at(0).text(),).toBe('No Data');
// 获取DOM节点
const input = wrapper.find('.ant-alert').getDOMNode();
// 2. getAttribute获取属性
expect(input.getAttribute('data-test')).toBe('test-id');
it('should have default notFoundContent', () => {const wrapper = mount(<Select mode="multiple" />);wrapper.find('.ant-select').simulate('click');jest.runAllTimers();const dropdownWrapper = mount(wrapper.find('Trigger').instance().getComponent(),);expect(dropdownWrapper.find('MenuItem').length).toBe(1);expect(dropdownWrapper.find('MenuItem').at(0).text(),).toBe('No Data');});
it('should throw error when option value is missing', () => {try {mount(<CheckCard.Group options={[{ value: 'Apple' }, { value: 'Pear' }, { title: 'Orange' }]} />,).unmount();} catch (e) {expect(e).toBeDefined();}});
React组件的测试和nodejs的单元测试有很大不同,我在对组件进行测试的时候,经常迷惑的一个点是我应该测试些什么?下面是在实战过程中总结的一些经验。
首先要确定自己的测试目标,比较直接的测试目标是
● 测试覆盖率,一般需要达到95%以上是一个比较好的状态,包括
○ 行覆盖率
○ 分支覆盖率
● 功能覆盖,有时候覆盖率达到了,不过很多不同使用场景仍然需要测试,比方说各种props是否正常,可以结合组件的demo,设计文件去设计测试用例
版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。
工作时间:8:00-18:00
客服电话
电子邮件
admin@qq.com
扫码二维码
获取最新动态