单元测试--终章

单元测试--终章

一月 17, 2021 本文共计: 877 字 预计阅读时长: 4分钟

被测组件划分

展示组件:通常定义为只用于展示的组件,例如 Icon、Badge……,遵循 Props –> UI;

业务组件:为业务服务,通常集成了独立的功能,可以单独作为模块组件,也可自定义配置;

功能组件:通常是支撑业务组件的基类组件,提供一些功能性的支撑,像分页组件的跳转;

测试场景

网络请求模拟/发起真实请求

  1. 可以模拟封装网络请求的方法,借助jest.mock模拟引用文件,返回需要结果的Promise
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @desc example
*/
import { mount } from 'enzyme';
import App from 'Componets/app.jsx';
import postData from '../api';

// we can mock it like this as follow
jest.mock('../api', () => jest.fn(() => Promise.then({data: { name: 'eric', age: 18 }})));

describe('An Test Example', () => {
it('Should called if mount', () => {
const wrapper = mount(<App/>);
expect(postData).toBeCalledTimes(1);
});
});
  1. 直接模拟真实请求,借助enzyme断言回调的done函数或者async
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* @desc done example
* @date 2021-01-13 19:56:58
* @author eric wang <jadeface.scholar@gmail.com>
* @copyright 2021 Eric
*/
import { mount } from 'enzyme';
import fetchData from '../api';

test('Test Fetch Data', (done) => {
fetchData()
.then(r => r)
.finally(() => done())
});

/**
* @desc async example
* @date 2021-01-13 20:02:29
* @author eric wang <jadeface.scholar@gmail.com>
* @copyright 2021 Eric
*/

test('Test Fetch Data', async () => {
try {
const res = await fetchData();
} catch (e) {
console.error(e);
}
});
  1. 根目录新建__mocks__文件夹,内部新建request.js,模拟用户请求。

测试异步请求其实还有其余的两种方案可以选择,像请求成功与请求失败:

1
2
3
4
5
6
7
8
9
jest.mock('../request');

import * as user from '../user';

//断言必须返回一个primose
it('works with promises', () => {
expect.assertions(1);
return user.getUserName(4).then(data => expect(data).toEqual('Mark'));
});
1
2
3
4
it('works with resolves', () => {
expect.assertions(1);
return expect(user.getUserName(5)).resolves.toEqual('Paul');
});

jest测试环境之node&browser切换

jest默认browser环境,如果需要切换node环境,在顶部追加一段注释就可以了。

1
2
3
/**
* @jest-environment jsdom
*/
1
2
3
/**
* @jest-environment node
*/

也可以自己内置沙盒去做为jest预置环境。
文档指引

1
2
3
/**
* @jest-environment ./my-custom-environment
*/

定时器模拟

1
2
3
4
5
jest.useFakeTimers();

test('Test one timer unit case', () => {
jest.runAllTimers();
});

未实现模块模拟

诸如window上的某些模块并未实现,像location模块,我们可以借助node环境的global模块下去模拟一个纯净的模块。

像包中的peerDependences我们可以借助,虚拟模块或者真实自定义模块模拟。

1
2
3
4
5
6
7
8
const user = jest.createMockFromModule('../user');

user.getAuthenticated = () => ({
age: 622,
name: 'Mock name',
});

export default user;
1
2
3
4
5
6
7
8
9
10
11
// 例如我们也可以在 TDD 模式下去模拟写未完成的虚拟模块,纯虚拟的
jest.mock(
'../moduleName',
() => {
/*
* Custom implementation of a module that doesn't exist in JS,
* like a generated module or a native module in react-native.
*/
},
{virtual: true},
);

小结

在观摩antd的测试用例的时候,有看到过这样的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// https://github.com/ant-design/ant-design/issues/20948
it('not repeat render when Form.Item is not a real Field', async () => {
const shouldNotRender = jest.fn();
const StaticInput = () => {
shouldNotRender();
return <Input />;
};

const shouldRender = jest.fn();
const DynamicInput = () => {
shouldRender();
return <Input />;
};

const formRef = React.createRef();

mount(
<div>
<Form ref={formRef}>
<Form.Item>
<StaticInput />
</Form.Item>
<Form.Item name="light">
<DynamicInput />
</Form.Item>
</Form>
</div>,
);

expect(shouldNotRender).toHaveBeenCalledTimes(1);
expect(shouldRender).toHaveBeenCalledTimes(1);

formRef.current.setFieldsValue({ light: 'bamboo' });
await Promise.resolve();
expect(shouldNotRender).toHaveBeenCalledTimes(1);
expect(shouldRender).toHaveBeenCalledTimes(2);
});