-
Notifications
You must be signed in to change notification settings - Fork 592
Write a test spec
赵鹏程(珵之) edited this page Dec 7, 2023
·
5 revisions
- 意图清晰:标题无歧义,代码逻辑清晰
- 稳定:单独运行,并发运行,CI运行都能保持一致结果
- 有效:用例充分证明了测试的意图
- 简短:尽量少的代码
- 无副作用:运行完毕后,无副作用影响其它用例
- 覆盖率90%+:保证变更or功能代码的覆盖率达标
import React, { useState } from 'react';
import Enzyme, { mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({ adapter: new Adapter() });
describe('Demo', function() {
it('渲染组件', function() {
const div = document.createElement('div');
document.body.appendChild(div);
const wrapper = mount(<div></div>, { attachTo: div }); // 渲染到document中
// ...编写用例代码...
// 清除副作用
wrapper.unmount();
document.body.removeChild(div);
});
})
it('demo', function() {
const wrapper = mount(<Button>btn</Button>, {attachTo: ...});
// setProps会先销毁再挂载
wrapper.setProps({ size: 'small' });
});
it('demo', function() {
const ref = { current: null };
function Demo() {
const [size, setSize] = useState('small');
ref.current = { setSize };
return <Button size={size}>btn</Button>
}
const wrapper = mount(<Demo/>, {attachTo: ...});
// 调整组件内部状态,保留组件实例
ref.current.setSize('large');
});
使用 react-dom/test-utils 工具类来触发事件
import ReactTestUtils from 'react-dom/test-utils';
it('demo', function() {
// ...
const btnElement = rootNode.querySelector('.next-btn');
// 鼠标事件
ReactTestUtils.Simulate.click(btnElement);
ReactTestUtils.Simulate.mouseEnter(btnElement);
// 滚动事件
const div = rootNode.querySelector('...');
div.scrollLeft = 100;
ReactTestUtils.Simulate.scroll(btn);
});
import React, { useState } from 'react';
import ReactTestUtils from 'react-dom/test-utils';
import Enzyme, { mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import assert from 'power-assert';
// import some components
import Button from '../../src/button';
Enzyme.configure({ adapter: new Adapter() });
function delay(duration) {
return new Promise(resolve => setTimeout(resolve, duration));
}
// 一个文件最多一个 根describe
// title:
// Button
// Button A11y
// Button Issues
describe('{title}', function() {
let wrapper = null,
rootNode = null;
beforeEach(function() {
rootNode = document.createElement('div');
document.body.appendChild(rootNode);
});
afterEach(function() {
if (wrapper) {
wrapper.unmount();
wrapper = null;
}
});
// 测试用例分组
describe('spec groups', function() {});
// 渲染组件,测试api功能
it('should xxxx when xxx', async function() {
// 渲染一个small button到 rootNode中,不传递 attachTo则不会挂载到document内,要求必须传递 attachTo,以真实环境测试
wrapper = mount(<Button size="small" />, { attachTo: rootNode });
// 若是渲染过程或事件执行过程有异步生效的场景,使用delay延迟一段时间再作断言
await delay(100);
// 查找元素,统一使用 rootNode.querySelector,在rootNode内的dom结构中获取(Overlay场景,要么使用followTrigger,要么使用wrapperClassName来获取弹层节点)
const smallButtonElement = rootNode.querySelector('.next-small');
assert(smallButtonElement);
// 调整 props,注意,setProps会创建全新的组件实例,原先的状态都会丢失
wrapper.setProps({ size: 'large' });
const largeButtonElement = rootNode.querySelector('.next-large');
assert(largeButtonElement);
});
// 测试状态变化场景,保持组件实例情况下,变更状态
it('should xxxx when xxx', async function() {
const ref = { current: null };
function Demo() {
const [state, setState] = useState({ size: 'small' });
ref.current = { setState };
return <Button size={state.size}>text</Button>;
}
wrapper = mount(<Demo />, { attachTo: rootNode });
await delay(100);
assert(rootNode.querySelector('.next-small'));
// 触发状态变化
ref.current.setState({ size: 'large' });
// 延迟一段时间,让React完成更新
await delay(100);
// 断言判断
assert(rootNode.querySelector('.next-large'));
});
// 触发事件
it('should xxxx when xxx', async function() {
wrapper = mount(<Button>text</Button>, { attachTo: rootNode });
await delay(100);
const btn = rootNode.querySelector('.next-btn');
assert(btn);
// 使用 react的测试工具 触发事件
// 鼠标事件
ReactTestUtils.Simulate.click(btn);
ReactTestUtils.Simulate.mouseEnter(btn);
ReactTestUtils.Simulate.mouseLeave(btn);
// 滚动事件
btn.scrollLeft = 100;
ReactTestUtils.Simulate.scroll(btn);
});
});