Будьте внимательны! Это приведет к удалению страницы «About-Unit-Test».
每个组件的单测用例都位于各自的test文件夹下,以componentName.test.js命名
1、添加一个测试.
import Switch from '../index';
// 此处无需再单独引入 shallow、mount、render、enzyme、sinon等
// 已经在<rootDir>/test/setup.js中统一赋值给了global,在组件级别的单测用例js中可以直接共享
describe('Switch', () => {
it('xxx should xxx', () => {
//...
});
it('unit test 2', () => {});
// ...
})
2、运行所有测试,看看新加的这个是不是失败了;如果能成功则重复步骤1.
// 只跑单元测试
npm run test:unit
// 跑所有组件的单测用例,并且输出覆盖率报告
npm run test:coverage
3、根据失败报错,有针对性的编写或改写代码。
4、再次运行测试;如果能成功则跳到步骤5,否则重复步骤3
5、重复步骤1
it('input with custom className & style', () => {
const wrapper = shallow(<Input className='test' style={{color: 'red'}}/>);
expect(wrapper.exists('.test')).toEqual(true);
expect(wrapper.find('div.test')).toHaveStyle('color', 'red');
});
// 一般检查是否拥有对应的dom节点,是否有对应的className,或者state对应的值是否正确
it('input different size', () => {
const largeInput = mount(<Input size='large'/>);
const defaultInput = mount(<Input />);
const smallInput = mount(<Input size='small' />);
expect(largeInput.find('.semi-input-large')).toHaveLength(1);
expect(smallInput.find('.semi-input-small')).toHaveLength(1);
});
it('input with placeholder', () => {
let placeholderText = 'semi placeholder';
const input = mount(<Input placeholder={placeholderText} />);
let inputDom = input.find('input');
expect(inputDom.props().placeholder).toEqual(placeholderText);
})
it('input', () => {
const input = mount(<Input />);
expect(input.state().disabled).toEqual(false); // 直接读取state
expect(input.props().disabled).toEqual(false); // 读取props
})
// 可以通过setState和setProps接口模拟组件的外部状态变化,测试组件状态动态改变时,UI是否做出了正确的响应
it('change props & state', () => {
const input = mount(<Input />);
input.setProps({ disabled: true }) ;
input.setState({ value: 1 })
input.update(); // !!!注意,需执行update()
// expect ....
}
it('Collapse with custom expandIcon / collapseIcon', () => {
let plusIcon = <Icon type="plus" />;
let minIcon = <Icon type="minus" />;
let props = {
expandIcon: plusIcon,
collapseIcon: minIcon
};
let collapse = mount(<Collapse {...props} />);
expect(collapse.props().expandIcon).toEqual(plusIcon);
expect(collapse.props().collapseIcon).toEqual(minIcon);
expect(collapse.contains(plusIcon)).toEqual(true);
expect(collapse.contains(minIcon)).toEqual(false);
});
it('input should call onChange when value change', () => {
let inputValue = 'semi';
let event = { target: { value: inputValue } };
let onChange = () => {};
// 使用sinon.spy封装回调函数,spy后可收集函数的调用信息
let spyOnChange = sinon.spy(onChange);
const input = mount(<Input onChange={spyOnChange} />);
// 找到原生input元素,触发模拟事件,模拟input的value值变化
input.find('.semi-input').simulate('change', event);
expect(spyOnChange.calledOnce).toBe(true); // onChange回调被执行一次
})
it('test callback value', () => {
// ... 主体代码同上
// 单个入参可以用calledWithMatch
expect(spyOnChange.calledWithMatch('semi')).toBe(true);
// 多个入参可以用
})
// 其他callXXX 型API的用法可以参考sinon的官方文档 https://sinonjs.org/releases/v7.5.0/spies/
it('test callback count / specific callback arguments', () => {
// ... 主体代码同上
// 获取函数的总调用次数
expect(spyOnChange.callCount).toBe(2);
// 获取该函数的第1次调用
let firstCall = spyOnChange.getCall(0);
// 第N次调用的入参
let arguments = firstCall.argus; // Array like object
})
it('collapse defaultActiveKey', () => {
const collapse = mount(<Collaplse defaultActiveKey='1'>
{...}// 中间省略
</Collapse>);
let domNode = collapse.find([tabIndex="1"]).getDOMNode();
// 使用getDOMNode获取ReactWrapper的真实DOM
// 注意某些属性的大小写,例如在dom中渲染是tabindex,但实际上获取属性时需要用tabIndex
expect(domNode.getAttribute("aria-expand")).toEqual(true);
})
````
it('show different direction popup layer', ()=> { let props = {
position: 'top',
data: stringData,
defaultOpen: true,
...commonProps
};
let ac = mount(<AutoComplete {...props} />, attachTo: {...} );
expect(ac.find('.semi-popover-wrapper').instance().getAttribute('x-placement')).toEqual('top');
ac.setProps({ props: 'right' });
ac.update();
expect(ac.find('.semi-popover-wrapper').instance().getAttribute('x-placement')).toEqual('right');
}) ````
it('optionList should show when defaultOpen is true & data not empty', () => {
// ...
// 使用find定位,children获取子节点列表,at获取第N个,getDOMNode获取DOM节点实例
let ac = mount(<AutoComplete {...props} />, { attachTo: document.getElementById('container') });
let list = ac.find('.semi-autocomplete-option-list').children();
expect(list.length).toEqual(4);
expect(list.at(0).getDOMNode().textContent).toEqual('semi');
expect(list.at(1).getDOMNode().textContent).toEqual('ies');
});
enzyme.smiluate 并非真正的模拟事件 https://github.com/airbnb/enzyme/issues/1606.
事件模拟不会像在真实环境中通常所期望的那样传播(即没有冒泡等机制)。因此,必须在具有事件处理程序集的实际节点上调用.simulate().
.simulate()实际上会根据你给它的事件来定位组件的prop。例如,.simulate('click')实际上会获得onClick prop并调用它.
断言库的差异. enzyme的官方文档示例,使用的是mocha + chai。semi直接用的jest + jest自带的expect风格的断言库, 所以语法上会有差异。 如 to.have.lengthOf(1) => toHaveLength(1)
最外层使用了Context.Consumer的组件,不适合用shallow. Semi有些组件,需要做i18n适配,组件结构如下,如果直接用shallow渲染,只能渲染出localeConsumer这层。这种情况需要直接用mount
<LocaleConsumer>
{
(locale, localeCode) => (
<div>{...}</div>
)}
</LocaleConsumer>
mount只将组件渲染到div元素,而不将其附加到DOM树
jest 内置的 istanbul 输出的覆盖率结果, 表格中的第2列至第5列,分别对应四个衡量维度:
当我们执行jest 命令时,加上 --coverage后,会输出覆盖率报告
// 跑某个组件的单元测试.
type=unit jest packages/semi-ui/xxx --silent --coverage.
跑完上述命令后,jest会输出对应的html(根目录/test/converage/xxx/index.html)
点击对应组件的html,没有被执行过的行会标红高亮展示(键盘按N可以直接跳至对应行)。因此注意在你的单侧case中覆盖到这些场景后,相应的单测行覆盖度就会得到提升.
Будьте внимательны! Это приведет к удалению страницы «About-Unit-Test».