Skip to content

Write a test spec

赵鹏程(珵之) edited this page Dec 7, 2023 · 5 revisions

一个好的测试用例

  1. 意图清晰:标题无歧义,代码逻辑清晰
  2. 稳定:单独运行,并发运行,CI运行都能保持一致结果
  3. 有效:用例充分证明了测试的意图
  4. 简短:尽量少的代码
  5. 无副作用:运行完毕后,无副作用影响其它用例
  6. 覆盖率90%+:保证变更or功能代码的覆盖率达标

最佳实践片段

for @alifd/next<=1.26

渲染组件

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);
});

Test spec demo(@alifd/next<=1.26)

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);
    });
});