Gowhich

Durban's Blog

项目初始化

1
2
3
4
5
git clone https://github.com/durban89/webpack4-react16-reactrouter-demo.git 
cd webpack4-react16-reactrouter-demo
git fetch origin
git checkout v_1.0.25
npm install

异步程序测试

首先,在Jest中启用Babel支。执行如下安装命令

1
npm install babel-jest babel-core regenerator-runtime --save-dev

下面实现一个简单的模块,从API中获取用户数据并返回用户名。

src/lib/user.js添加如下代码

1
2
3
4
5
import request from './request';

static getUserName(userID) {
return request(`/users/${userID}`).then(user => user.name);
}

在上面的实现中,我们希望request.js模块返回一个promise。然后通过传递用户ID来获取用户信息,最后得到一个用户名称。
request.js的获取用户信息的实现如下:src/lib/request.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const http = require('http');

export default function request(url) {
return new Promise((resolve) => {
http.get({
path: url,
}, (response) => {
let data = '';
response.on('data', (o) => {
data += o;
return data;
});
response.on('end', () => resolve(data));
});
});
}

这里的话希望在测试中不访问网络,所以在__mocks__文件夹中创建一个request.js,手动模拟网络请求(该文件夹区分大小写,’__MOCKS__‘是不起作用的)。
它可能看起来像这样src/lib/__mocks__/request.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const users = {
4: {
name: 'Mark',
},
5: {
name: 'Paul',
},
};

export default function request(url) {
return new Promise((resolve, reject) => {
const userID = parseInt(url.substr('/users/'.length), 10);
process.nextTick(() => {
if (users[userID]) {
return resolve(users[userID]);
}
return reject({
error: `User with ${userID} not found.`,
});
});
});
}

现在为这个异步的功能编写一个测试。src/__tests__/user_async.test.js

1
2
3
4
5
6
7
8
9
10
import Users from '../lib/user';

jest.mock('../lib/request');

// The assertion for a promise must be returned.
it('works with promises', () =>
// expect.assertions(1); // 当前版本加了这行总是报错,暂时未注释
Users
.getUserName(4)
.then(data => expect(data).toEqual('Mark')));

运行测试,正常通过测试,如果问题可加群沟通

jest.mock('../lib/request')会告诉Jest去使用我们手动模拟的mock,’it’期望返回一个Promise,这个Promise的返回结果是resloved。
只要最后返回一个Promise,我们就可以链接尽可能多的Promise,并且随时调用’expect’。

.resolves

有一种不那么冗长的方式是使用’resolves’,让它与任何其他匹配器一起unwrap一个fulfilled promise的值。
如果promise是rejected,则断言将失败。如下

1
2
3
4
it('works with resolves', () => {
expect.assertions(1);
return expect(user.getUserName(5)).resolves.toEqual('Paul');
});

async/await

使用async/await语法编写测试很容易。可以写一个和上面实例一致的测试。如下

1
2
3
4
5
6
7
8
9
10
11
12
13
import Users from '../lib/user';

jest.mock('../lib/request');

it('works with async/await', async () => {
const data = await Users.getUserName(4);
expect(data).toEqual('Mark');
});

it('works with async/await and resolves', async () => {
expect.assertions(1);
await expect(Users.getUserName(5)).resolves.toEqual('Paul');
});

为了使得测试中支持 async/await, 需要安装 babel-preset-env 并且在开通这个属性在.babelrc文件中.

Error handling

可以使用.catch方法处理错误。
确保添加expect.assertions以验证是否调用了一定数量的断言。
否则,一个fulfilled promise不会使测试失败:如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import Users from '../lib/user';

jest.mock('../lib/request');

test('tests error with promises', async () => {
expect.assertions(1);
return Users.getUserName(2).catch(e =>
expect(e).toEqual({
error: 'User with 2 not found.',
}));
});

it('tests error with async/await', async () => {
expect.assertions(1);
try {
await Users.getUserName(1);
} catch (e) {
expect(e).toEqual({
error: 'User with 1 not found.',
});
}
});

.rejects

.rejects帮助程序就像.resolves帮助程序一样。
如果promise是满足的,测试将自动失败。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import Users from '../lib/user';

jest.mock('../lib/request');
it('tests error with rejects', () => {
expect.assertions(1);
return expect(Users.getUserName(3)).rejects.toEqual({
error: 'User with 3 not found.',
});
});

it('tests error with async/await and rejects', async () => {
expect.assertions(1);
await expect(Users.getUserName(3)).rejects.toEqual({
error: 'User with 3 not found.',
});
});

项目实践地址

1
2
https://github.com/durban89/webpack4-react16-reactrouter-demo.git
tag:v_1.0.26

项目初始化

1
2
3
4
5
git clone https://github.com/durban89/webpack4-react16-reactrouter-demo.git 
cd webpack4-react16-reactrouter-demo
git fetch origin
git checkout v_1.0.24
npm install

只要想确保UI不会意外更改,快照测试是非常有用的工具。

移动应用程序的典型呈现UI组件快照测试用例,通过截取屏幕截图,然后将其与存储在测试这边的参考图像进行比较。

如果两个图像不匹配,测试将失败,可能是被意外的更改了,或者需要将屏幕截图更新为新版本的UI组件。

Jest快照测试

在测试React组件时,可以采用类似的方法。
可以使用测试渲染器快速生成React树的可序列化值,而不是渲染需要构建整个应用程序的图形UI。

下面做一个简单的Link组件的示例测试:
测试之前先安装下react-test-renderer依赖库

1
npm install react-test-renderer --save-dev

src/__tests__/react_link.react.jsx

1
2
3
4
5
6
7
8
9
import React from 'react';
import renderer from 'react-test-renderer';
import ALink from '../components/Link.react';

it('正确的渲染', () => {
const tree = renderer
.create(<ALink page="https://www.gowhich.com">Gowhich</ALink>)
.toJSON(); expect(tree).toMatchSnapshot();
});

src/components/Link.react.jsx

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import React from 'react';
import PropTypes from 'prop-types';

const STATUS = {
HOVERED: 'hovered',
NORMAL: 'normal',
};

class Link extends React.Component {
constructor(props) {
super(props);

this.onMouseEnter = this.onMouseEnter.bind(this);
this.onMouseleave = this.onMouseleave.bind(this);
this.state = {
class: STATUS.NORMAL,
};
}

onMouseEnter() {
this.setState({
class: STATUS.HOVERED,
});
}

onMouseleave() {
this.setState({
class: STATUS.NORMAL,
});
}

render() {
return (
<a
className={this.state.class}
href={this.props.page || '#'}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseleave}
>
{this.props.children}
</a>
);
}
}

Link.propTypes = {
page: PropTypes.string.isRequired,
children: PropTypes.oneOfType([
PropTypes.element,
PropTypes.string,
]).isRequired,
};

export default Link;

第一次运行此测试时,Jest会创建一个如下所示的快照文件:
src/__tests__/__snapshots__/react_link.test.jsx.snap

1
2
3
4
5
6
7
8
9
10
11
12
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`正确的渲染 1`] = `
<a
className="normal"
href="https://www.gowhich.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Gowhich
</a>
`;

快照文件应该与代码更改一起提交,并作为代码审查过程的一部分进行审核。
Jest使用pretty-format对快照文件进行了处理,当代码在审查期间,会让代码快照变成让人类可阅读的文件。
在随后的测试运行中,Jest会将渲染的输出的文件与之前的快照进行比较。
如果匹配,测试将通过。如果它们不匹配,表示代码出问题了。Jest则会告知代码中哪里需要进行修改的错误或者是代码的逻辑需要进行更新,重新生成快照。我们是试着更改下page参数的地址和Gowhich文案,如下
src/__tests__/react_link.react.jsx

1
2
3
4
5
6
7
8
9
import React from 'react';
import renderer from 'react-test-renderer';
import ALink from '../components/Link.react';

it('正确的渲染', () => {
const tree = renderer
.create(<ALink page="https://www.walkerfree.com">Walkerfree</ALink>)
.toJSON(); expect(tree).toMatchSnapshot();
});

再次执行测试,会得到类似如下的输出,这里我就只截取错误这部分的内容

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
  ✕ 正确的渲染 (22ms)

● 正确的渲染

expect(value).toMatchSnapshot()

Received value does not match stored snapshot "正确的渲染 1".

- Snapshot
+ Received

<a
className="normal"
- href="https://www.gowhich.com"
+ href="https://www.walkerfree.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
- Gowhich
+ Walkerfree
</a>

6 | const tree = renderer
7 | .create(<ALink page="https://www.walkerfree.com">Walkerfree</ALink>)
> 8 | .toJSON(); expect(tree).toMatchSnapshot();
| ^
9 | });
10 |

at Object.<anonymous> (src/__tests__/react_link.test.jsx:8:29)

› 1 snapshot failed.
Snapshot Summary
› 1 snapshot failed from 1 test suite. Inspect your code changes or re-run jest with `-u` to update them.

由于刚刚更新了组件以指向不同的地址和文案,而且这次更改是合理的,是我们希望的逻辑,这个时候快照测试会失败,因为刚更新的组件的快照不再与此测试用例的快照相匹配。

为了解决这个问题,我们需要更新快照文件,只需要运行下面的命令,就会重新生成快照

1
npx jest src/__tests__/react_link.test.jsx --notify --watchman=false --updateSnapshot

这个时候我们就需要同之前一样,将这个新的快照文件同代码的更改一起提交。

快照交互模式

执行如下命令

1
npx jest src/__tests__/react_link.test.jsx --notify --watchman=false --updateSnapshot --watch

只是加了一个

1
--watch

运行上面的命令会得到类似如下的输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 PASS  src/__tests__/react_link.test.jsx
✓ 正确的渲染 (17ms)

Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 1 passed, 1 total
Time: 1.713s
Ran all test suites matching /src\/__tests__\/react_link.test.jsx/i.

Active Filters: filename /src/__tests__/react_link.test.jsx/
› Press c to clear filters.

Watch Usage
› Press a to run all tests.
› Press f to run only failed tests.
› Press o to only run tests related to changed files.
› Press p to filter by a filename regex pattern.
› Press t to filter by a test name regex pattern.
› Press q to quit watch mode.
› Press Enter to trigger a test run.

而且会对测试文件进行检测。如果有文件改动了,会自动重新启动测试,然后对测试文件,进行重新测试属性匹配器

通常有时候,一个对象的属性的值被生成,但是在进行快照生成时候,这个值就固定了,下载再次执行测试的时候就会报错,如下

1
2
3
4
5
6
7
8
9
it('这个快照再次测试的时候会失败', () => {
const user = {
createAt: new Date(),
id: Math.floor(Math.random() * 20),
name: 'Durban',
};

expect(user).toMatchSnapshot();
});

执行后生成的快照如下

1
2
3
4
5
6
7
8
9
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`这个快照再次测试的时候会失败 1`] = `
Object {
"createAt": 2018-07-06T06:44:49.429Z,
"id": 0,
"name": "Durban",
}
`;

其实从下面这里我们就已经能发现问题了

1
"createAt": 2018-07-06T06:44:49.429Z,

日期是每次执行一次都会变化的。当我们再次执行的时候会发现报出了如下错误

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
  ✕ 这个快照再次测试的时候会失败 (14ms)

● 这个快照再次测试的时候会失败

expect(value).toMatchSnapshot()

Received value does not match stored snapshot "这个快照再次测试的时候会失败 1".

- Snapshot
+ Received

Object {
- "createAt": 2018-07-06T06:44:49.429Z,
- "id": 0,
+ "createAt": 2018-07-06T06:46:02.245Z,
+ "id": 10,
"name": "Durban",
}

6 | };
7 |
> 8 | expect(user).toMatchSnapshot();
| ^
9 | });
10 |

at Object.<anonymous> (src/__tests__/jest_snap_property.test.js:8:16)

› 1 snapshot failed.
Snapshot Summary
› 1 snapshot failed from 1 test suite. Inspect your code changes or re-run jest with `-u` to update them.

针对这个情况我们可以这样解决,在测试里面加入如下代码

1
2
3
4
expect(user).toMatchSnapshot({
createAt: expect.any(Date),
id: expect.any(Number),
});

修改后代码如下

1
2
3
4
5
6
7
8
9
10
11
12
it('检查匹配器并测试通过', () => {
const user = {
createAt: new Date(),
id: Math.floor(Math.random() * 20),
name: 'Durban',
};

expect(user).toMatchSnapshot({
createAt: expect.any(Date),
id: expect.any(Number),
});
});

执行更新操作,对快照进行更新得到的快照内容结果如下

1
2
3
4
5
6
7
8
9
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`检查匹配器并测试通过 1`] = `
Object {
"createAt": Any<Date>,
"id": Any<Number>,
"name": "Durban",
}
`;

项目实践地址

1
2
https://github.com/durban89/webpack4-react16-reactrouter-demo.git
tag:v_1.0.25

项目初始化【这里使用之前的项目,节省时间】

项目初始化地址

1
2
https://github.com/durban89/webpack4-react16-reactrouter-demo.git
tag:v_1.0.23

拉取

1
2
3
4
5
git clone https://github.com/durban89/webpack4-react16-reactrouter-demo.git 
cd webpack4-react16-reactrouter-demo
git fetch origin
git checkout v_1.0.23
npm install

Jest有很多特定的功能,可以作为单独的包来使用,下面罗列下官网提供的一些有用的包

环境

1
2
node --version
v8.11.3

node的安装包方法我就不介绍了,如果看了这么久我的分享还是不知道的话,可以继续看看前面文章,这里简单提示下,安装命令如下

1
2
npm install package_name --save // 生产安装
npm install package_name --save-dev // 开发安装

1、jest-changed-files

此工具提供的功能是标识在git或hg中被修改过的文件,提供的方法如下

getChangedFilesForRoots 返回一个promise,该promise将解析为具有已更改文件和repos的对象。

findRepos 返回一个promise,该promise将解析为指定路径中包含的一组存储库。

演示如下

1
2
3
4
5
const { getChangedFilesForRoots } = require('jest-changed-files');

getChangedFilesForRoots(['./'], {
lastCommit: true,
}).then(result => console.log(result.changedFiles));

运行后得到类似如下结果

1
2
Set {
'/xxx/webpack-react-demo/src/__tests__/jest_mock_names.test.js' }

2、jest-diff

用于可视化数据变化的工具。导出一个比较任意类型的两个值的函数,并返回一个”pretty-printed”的字符串,说明两个参数之间的差异。

演示如下

1
2
3
4
5
6
7
8
const diff = require('jest-diff');

const a = { a: { b: { c: 5 } } };
const b = { a: { b: { c: 6 } } };

const result = diff(a, b);

console.log(result);

运行后输出的结果类似如下

1
2
3
4
5
6
7
8
9
10
11
- Expected
+ Received

Object {
"a": Object {
"b": Object {
- "c": 5,
+ "c": 6,
},
},
}

3、jest-docblock

用于提取和解析JavaScript文件顶部注释的工具。导出各种函数来操作注释块内的数据。

演示如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const { parseWithComments } = require('jest-docblock');

const code = `
/**
* 这是一个例子
*
* @author durban
*
*/
console.log('Hello Jest!');
`;

const parsed = parseWithComments(code);
console.log(parsed);

运行后输出结果类似如下

1
2
{ comments: '  /**\n这是一个例子\n\n \n/\n  console.log(\'Hello Jest!\');',
pragmas: { author: 'durban' } }

4、jest-get-type

标识任何JavaScript值的基本类型的模块。导出一个函数,该函数返回一个字符串,其值的类型作为参数传递。

演示如下

1
2
3
4
5
6
7
8
9
const getType = require('jest-get-type');

const array = [1, 2, 3];
const nullvalue = null;
const undefinedValue = undefined;

console.log(getType(array));
console.log(getType(nullvalue));
console.log(getType(undefinedValue));

运行后输出结果类似如下

1
2
3
array
null
undefined

5、jest-validate

用于验证用户提交的配置的工具。

导出一个带有两个参数的函数:用户的配置和包含示例配置和其他选项的对象。

返回值是一个具有两个属性的对象,如下:

hasDeprecationWarnings 一个布尔值,指示提交的配置是否具有弃用警告

isValid 一个布尔值,指示配置是否正确。

演示如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const { validate } = require('jest-validate');

const configByUser = {
tranform: '<rootDir>/node_modules/my-custom-packages',
};

const result = validate(configByUser, {
comment: ' Documentation: http://custom-docs.com',
exampleConfig: {
tranform: '<rootDir>/node_modules/jest-validate',
},
});

console.log(result);

运行后输出结果类似如下

1
{ hasDeprecationWarnings: false, isValid: true }

6、jest-worker

用于并行化任务的模块。

导出一个类Worker,它接受Node.js模块的路径,并允许您调用模块的导出方法,就好像它们在类方法中一样,返回一个promise,当指定的方法在forked进程中完成它的执行时解析。

演示如下

创建一个task.js

1
2
3
module.exports = {
Task: args => args,
};

调用的代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const { default: Worker } = require('jest-worker');

async function main() {
const worker = new Worker(require.resolve('./task.js'));

const results = await Promise.all([
worker.Task({ args: '1' }),
worker.Task({ args: '2' }),
]);

console.log(results);
}

main();

运行后输出结果类似如下

1
[ { args: '1' }, { args: '2' } ]

这里用了一个看起来很奇怪的用法,其实不然,仔细了解下node包的机制,还是很好理解的

1
const { default: Worker } = require('jest-worker');

如果你的node版本支持import的话可以替换为,【我试了下10.5.0这个nodejs版本还是不支持呀】

1
import Worker from 'jest-worker';

7、pretty-format

导出将任何JavaScript值转换为人类可读字符串的函数。

支持开箱即用的所有内置JavaScript类型,并允许通过用户定义的特定应用程序类型的扩展。

演示如下

1
2
3
4
5
6
7
8
9
const prettyFormat = require('pretty-format');

const val = { object: {} };
val.circuleReference = val;
val[Symbol('key')] = 'key';
val.map = new Map([['property', 'value']]);
val.array = [-0, Infinity, NaN];

console.log(prettyFormat(val));

运行后输出结果类似如下

1
2
3
4
5
6
7
8
9
10
11
12
13
Object {
"array": Array [
-0,
Infinity,
NaN,
],
"circuleReference": [Circular],
"map": Map {
"property" => "value",
},
"object": Object {},
Symbol(key): "key",
}

Jest的包远不止这些,有兴趣的可以继续去https://github.com/facebook/jest/tree/master/packages这里观望自己想要的

项目实践地址

1
2
https://github.com/durban89/webpack4-react16-reactrouter-demo.git
tag:v_1.0.24

项目初始化【这里使用之前的项目,节省时间】

项目初始化地址

1
2
https://github.com/durban89/webpack4-react16-reactrouter-demo.git
tag:v_1.0.22

拉取

1
2
3
4
5
git clone https://github.com/durban89/webpack4-react16-reactrouter-demo.git 
cd webpack4-react16-reactrouter-demo
git fetch origin
git checkout v_1.0.22
npm install

Mock Names

您可以选择为mock function提供一个名称,该名称将在测试错误输出中显示,而不是”jest.fn()”。如果您希望能够快速识别在测试输出中报告错误的mock function,请使用此选项。如下

1
2
3
4
5
6
7
8
9
const myMockFunc = jest
.fn()
.mockReturnValue('default')
.mockImplementation(v => 42 + v)
.mockName('add42');

test('add 42', () => {
expect(myMockFunc(1)).toEqual(43);
});

Custom Matchers

最后,为了简化断言如何调用mock函数,Jest提供了一些自定义匹配器函数,如下

1
2
3
4
5
6
7
8
9
10
11
// mock function至少被调用一次
expect(mockFunc).toBeCalled();

// mock function至少在带有具体参数的情况下被调用一次
expect(mockFunc).toBeCalledWith(arg1, arg2);

// mock function最后在带有具体参数的情况下被调用
expect(mockFunc).lastCalledWith(arg1, arg2);

// 所有的调用和mock被作为snapshot写入到文件
expect(mockFunc).toMatchSnapshot();

这些匹配器实际上只是用于检查.mock属性的常见形式的糖。

可以自己手动完成此操作,如果想这更符合自己的口味或者需要做一些更具体的事情,比如如下这些

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// mock function至少被调用一次
expect(mockFunc.mock.calls.length).toBeGreaterThan(0);

// mock function至少在带有具体参数的情况下被调用一次
expect(mockFunc.mock.calls).toContain([arg1, arg2]);

// mock function最后在带有具体参数的情况下被调用
expect(mockFunc.mock.calls[mockFunc.mock.calls.length - 1]).toEqual([
arg1,
arg2,
]);

// mock function被最后一次调用传入的第一个参数是`42`
expect(mockFunc.mock.calls[mockFunc.mock.calls.length - 1][0]).toBe(42);

// 一个snapshot将会检查mock在以同样的参数同样的次数被调用,它也将在名称上断言
expect(mockFunc.mock.calls).toEqual([[arg1, arg2]]);
expect(mockFunc.mock.getMockName()).toBe('a mock name');

如果想要一个完成的matchers,可以到官网点击这里去查看

项目实践地址

1
2
https://github.com/durban89/webpack4-react16-reactrouter-demo.git
tag:v_1.0.23

项目初始化【这里使用之前的项目,节省时间】

项目初始化地址

1
2
https://github.com/durban89/webpack4-react16-reactrouter-demo.git
tag:v_1.0.21

拉取

1
2
3
4
5
git clone https://github.com/durban89/webpack4-react16-reactrouter-demo.git 
cd webpack4-react16-reactrouter-demo
git fetch origin
git checkout v_1.0.21
npm install

Mocking Modules

假设我们有一个从API中获取用户的类。该类使用axios调用API然后返回包含所有用户的data属性:

因为要用到axios,需要安装下axios,

运行

1
npm install axios --save

然后创建文件src/lib/user.js

1
2
3
4
5
6
7
8
9
import axios from 'axios';

class Users {
static all() {
return axios.get('/user.json').then(resp => resp.data);
}
}

export default Users;

创建文件src/__tests__/user.test.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import axios from 'axios';
import Users from '../lib/user';

jest.mock('axios');

test('should fetch users', () => {
const resp = {
data: [
{
name: 'Durban',
},
],
};

axios.get.mockResolvedValue(resp);
// 或者也可以使用下面的代码
// axios.get.mockImplementation(() => Promise.resolve(resp));

return Users.all().then(users => expect(users).toEqual(resp.data));
});

现在,为了不在实际访问API的情况下测试此方法(从而创建缓慢且脆弱的测试),我们可以使用jest.mock(…)函数自动模拟axios模块。

一旦我们模拟了模块,我们就可以为.get提供一个mockReturnValue,它返回我们测试希望要的断言数据。实际上,我们说我们希望axios.get(’/users.json’)返回一个假响应。

Mock Implementations

尽管如此,有些情况下超出指定返回值的能力和全面替换模拟函数的实现是有用的。这可以使用jest.fn或mock函数上的mockImplementationOnce方法来实现。如下

1
2
3
4
5
6
const myMockFn = jest.fn(cb => cb(null, true));

myMockFn((err, val) => console.log(val));
// > true
myMockFn((err, val) => console.log(val));
// > true

当需要定义从另一个模块创建的模拟函数的默认实现时,mockImplementation方法很有用,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
// foo.js
module.exports = function() {
// some implementation;
};

// test.js
jest.mock('../foo'); // this happens automatically with automocking
const foo = require('../foo');

// foo is a mock function
foo.mockImplementation(() => 42);
foo();
// > 42

当需要重新创建模拟函数的复杂行为,以便多个函数调用产生不同的结果时,请使用mockImplementationOnce方法,如下

1
2
3
4
5
6
7
8
9
10
const myMockFn = jest
.fn()
.mockImplementationOnce(cb => cb(null, true))
.mockImplementationOnce(cb => cb(null, false));

myMockFn((err, val) => console.log(val));
// > true

myMockFn((err, val) => console.log(val));
// > false

当mocked函数超出了mockImplementationOnce定义的实现次数时,它将使用jest.fn执行默认实现集(如果已定义),如下

1
2
3
4
5
6
7
const myMockFn = jest
.fn(() => 'default')
.mockImplementationOnce(() => 'first call')
.mockImplementationOnce(() => 'second call');

console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn());
// > 'first call', 'second call', 'default', 'default'

对于我们有通常链接的方法(因此总是需要返回这个)的情况,我们有一个含糖API,以.mockReturnThis()函数的形式简化它,该函数也位于所有模拟上,如下

1
2
3
const myObj = {
myMethod: jest.fn().mockReturnThis(),
};

跟如下是类似的

1
2
3
4
5
const otherObj = {
myMethod: jest.fn(function() {
return this;
}),
};

项目实践地址

1
2
https://github.com/durban89/webpack4-react16-reactrouter-demo.git
tag:v_1.0.22

项目初始化【这里使用之前的项目,节省时间】

项目初始化地址

1
2
https://github.com/durban89/webpack4-react16-reactrouter-demo.git
tag:v_1.0.20

拉取

1
2
3
4
5
git clone https://github.com/durban89/webpack4-react16-reactrouter-demo.git 
cd webpack4-react16-reactrouter-demo
git fetch origin
git checkout v_1.0.20
npm install

Mock Return Values

Jest的模拟函数(Mock function)也可以用来在测试中注入测试值到测试的代码中,如下

1
2
3
4
5
6
7
8
9
const myMock = jest.fn();
console.log(myMock);

myMock
.mockReturnValueOnce(10)
.mockReturnValueOnce('x')
.mockReturnValue(true);

console.log(myMock(), myMock(), myMock(), myMock(), myMock());

运行npx jest src/__tests__/jest_mock_return_values.test.js –notify –watchman=false 后输出的结果类似如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
console.log src/__tests__/jest_mock_return_values.test.js:2
{ [Function: mockConstructor]
_isMockFunction: true,
getMockImplementation: [Function],
mock: [Getter/Setter],
mockClear: [Function],
mockReset: [Function],
mockRestore: [Function],
mockReturnValueOnce: [Function],
mockResolvedValueOnce: [Function],
mockRejectedValueOnce: [Function],
mockReturnValue: [Function],
mockResolvedValue: [Function],
mockRejectedValue: [Function],
mockImplementationOnce: [Function],
mockImplementation: [Function],
mockReturnThis: [Function],
mockName: [Function],
getMockName: [Function] }

console.log src/__tests__/jest_mock_return_values.test.js:9
10 'x' true true true

模拟函数(Mock function)在使用函数continuation-passing风格的代码中也非常有效。以这种风格编写的代码有助于避免需要复杂的存根,以重新创建它们所代表的真实组件的行为,从而倾向于在使用它们之前直接将值注入测试中。具体看如下代码

1
2
3
4
5
6
7
8
9
const filterTestFn = jest.fn();

// 第一次mock返回true,第二次mock返回false
filterTestFn.mockReturnValueOnce(true).mockReturnValueOnce(false);

const result = [11, 12].filter(filterTestFn);

console.log(result);
console.log(filterTestFn.mock.calls);

运行npx jest src/__tests__/jest_mock_return_values.test.js –notify –watchman=false 后得到的结果类似如下

1
2
3
4
5
console.log src/__tests__/jest_mock_return_values.test.js:20
[ 11 ]

console.log src/__tests__/jest_mock_return_values.test.js:22
[ [ 11, 0, [ 11, 12 ] ], [ 12, 1, [ 11, 12 ] ] ]

现实世界中大多数的例子实际上是在依赖的组件上获取模拟函数并对其进行配置,但技术是相同的。在这些情况下,尽量避免在没有直接测试的任何函数内部实现逻辑。

项目实践地址

1
2
https://github.com/durban89/webpack4-react16-reactrouter-demo.git
tag:v_1.0.21

项目初始化【这里使用之前的项目,节省时间】

项目地址

1
2
https://github.com/durban89/webpack4-react16-reactrouter-demo.git
tag:v_1.0.19

拉取

1
2
3
4
5
git clone https://github.com/durban89/webpack4-react16-reactrouter-demo.git 
cd webpack4-react16-reactrouter-demo
git fetch origin
git checkout v_1.0.19
npm install

mock function 可以通过擦除函数的实际实现,捕获对函数的调用(以及在这些调用中传递的参数),在使用new实例化时捕获构造函数的实例,以及允许test-time来轻松测试代码之间的链接返回值的配置。

有两种方法来mock function:通过创建一个mock function来使用测试代码,或者编写一个手动模拟来覆盖模块依赖。

下面先记录下如何使用mock function

Using a mock function

假设我们正在测试一个函数forEach的实现,它调用所提供数组中每个项的回调。如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function forEach(items, callback) {
for (let i = 0; i < items.length; i += 1) {
callback(items[i]);
}
}

const mockFunction = jest.fn();
forEach([0, 1], mockFunction);

test('mockFunction被调用2次', () => {
expect(mockFunction.mock.calls.length).toBe(2);
});

test('第一个调用第一个参数是0', () => {
expect(mockFunction.mock.calls[0][0]).toBe(0);
});

test('第一个调用第一个参数是1', () => {
expect(mockFunction.mock.calls[1][0]).toBe(1);
});

运行

1
npx jest src/__tests__/jest_use_mock_function.test.js --notify --watchman=false

后输出结果类似如下

1
2
3
4
5
6
7
8
9
10
PASS  src/__tests__/jest_use_mock_function.test.js
✓ mockFunction被调用2次 (5ms)
✓ 第一个调用第一个参数是0
✓ 第一个调用第一个参数是1

Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 2.341s
Ran all test suites matching /src\/__tests__\/jest_use_mock_function.test.js/i.

.mock property

所有模拟函数都有这个特殊的.mock属性,它是关于如何调用函数以及保留函数返回的数据的地方。.mock属性还会跟踪每次调用的值,因此也可以检查它:

1
2
3
4
5
6
7
8
const myMock = jest.fn();

const a = new myMock();
const b = {};
const bound = myMock.bind(b);
bound();

console.log(myMock.mock.instances);

运行后会看到console.log输出的内容类似如下

1
[ mockConstructor {}, {} ]

这些模拟成员在测试中非常有用,用于断言这些函数如何被调用,实例化或他们返回了什么,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 函数被调用的次数
expect(someMockFunction.mock.calls.length).toBe(1);

// 函数第1次被调用的第一个参数值是'first arg'
expect(someMockFunction.mock.calls[0][0]).toBe('first arg');

// 函数第2次被调用的第一个参数值是'second arg'
expect(someMockFunction.mock.calls[0][1]).toBe('second arg');

// 函数第1次被调用的返回值是'return value'
expect(someMockFunction.mock.results[0].value).toBe('return value');

// 函数被实例化了2次
expect(someMockFunction.mock.instances.length).toBe(2);

// 函数第一次被实例化返回的对象里面有个'name'的属性,它的值为'test'
expect(someMockFunction.mock.instances[0].name).toEqual('test');

具体如何使用后面的分享会涉及到多多关注

项目地址

1
2
https://github.com/durban89/webpack4-react16-reactrouter-demo.git
tag:v_1.0.20

项目初始化【这里使用之前的项目,节省时间】

项目地址

1
2
https://github.com/durban89/webpack4-react16-reactrouter-demo.git
tag:v_1.0.18

拉取

1
2
3
4
5
git clone https://github.com/durban89/webpack4-react16-reactrouter-demo.git 
cd webpack4-react16-reactrouter-demo
git fetch origin
git checkout v_1.0.18
npm install

Scoping 作用域

默认情况下,before和after中的代码适用于每个测试模块。

describe可以将测试分组,将多个测分到一个有意义的组里面。

当describe块将测试分组在一起时,before和after中的代码仅适用于describe块内的测试。具体如下

假设有城市数据库和食品数据库。我们分别可以为不同的测试做不同的设置:

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
const citys = [];
const foods = [];
let time1 = 1;
let time2 = 1;
const isCity = (city) => {
if (citys.indexOf(city) > -1) {
return true;
}
return false;
};

const isCityAndFood = (cityAndFood) => {
let hasCity = false;
let hasFood = false;

if (citys.indexOf(cityAndFood.city) > -1) {
hasCity = true;
}

if (foods.indexOf(cityAndFood.food) > -1) {
hasFood = true;
}

if (hasCity && hasFood) {
return true;
}

return false;
};
const initCityDatabase = () => new Promise((resolve, reject) => {
let promise;

try {
setTimeout(() => {
console.log('initCityDatabase time = ', time1);
if (time1 === 1) {
citys.push('Shanghai');
} else if (time1 === 2) {
citys.push('Chifeng');
}
time1 += 1;
promise = resolve(citys);
}, 1000);
} catch (err) {
return reject(err);
}

return promise;
});

const initFoodDatabase = () => new Promise((resolve, reject) => {
let promise;

try {
setTimeout(() => {
console.log('initFoodDatabase time = ', time2);
if (time2 === 1) {
foods.push('Banana');
} else if (time2 === 2) {
foods.push('Apple');
}
time2 += 1;
promise = resolve(foods);
}, 1000);
} catch (err) {
return reject(err);
}

return promise;
});

beforeEach(() => initCityDatabase());

test('city database has Shanghai', () => {
expect(isCity('Shanghai')).toBeTruthy();
});

test('city database has Chifeng', () => {
expect(isCity('Chifeng')).toBeTruthy();
});

describe('matching cities to foods', () => {
beforeEach(() => initFoodDatabase());

test('database has Shanghai and Banana', () => {
expect(isCityAndFood({
city: 'Shanghai',
food: 'Banana',
})).toBe(true);
});

test('database has Chifeng and Apple', () => {
expect(isCityAndFood({
city: 'Chifeng',
food: 'Apple',
})).toBe(true);
});
});

执行npm test得到类似如下结果

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
> xx@xx test /Users/durban/nodejs/webpack-react-demo
> jest --notify --watchman=false

PASS src/__tests__/jest_async_promise.test.js (6.41s)
PASS src/__tests__/jest_async_await.test.js (6.401s)
PASS src/__tests__/jest_setup_describe.js (7.09s)
● Console

console.log src/__tests__/jest_setup_describe.js:35
initCityDatabase time = 1
console.log src/__tests__/jest_setup_describe.js:35
initCityDatabase time = 2
console.log src/__tests__/jest_setup_describe.js:35
initCityDatabase time = 3
console.log src/__tests__/jest_setup_describe.js:56
initFoodDatabase time = 1
console.log src/__tests__/jest_setup_describe.js:35
initCityDatabase time = 4
console.log src/__tests__/jest_setup_describe.js:56
initFoodDatabase time = 2

PASS src/__tests__/jest_async_callback.test.js
PASS src/__tests__/CheckboxWithLabelComponent.test.jsx
PASS src/__tests__/jest_common.test.js
PASS src/__tests__/sum.test.js
PASS src/__tests__/jest_setup_each_onetime.test.js
PASS src/__tests__/jest_setup_each_moretime.test.js

Test Suites: 9 passed, 9 total
Tests: 30 passed, 30 total
Snapshots: 0 total
Time: 12.94s
Ran all test suites.

请注意这里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
● Console

console.log src/__tests__/jest_setup_describe.js:35
initCityDatabase time = 1
console.log src/__tests__/jest_setup_describe.js:35
initCityDatabase time = 2
console.log src/__tests__/jest_setup_describe.js:35
initCityDatabase time = 3
console.log src/__tests__/jest_setup_describe.js:56
initFoodDatabase time = 1
console.log src/__tests__/jest_setup_describe.js:35
initCityDatabase time = 4
console.log src/__tests__/jest_setup_describe.js:56
initFoodDatabase time = 2

initCityDatabase执行了4次,initFoodDatabase执行了2次,这是因为顶级beforeEach在describe块内的beforeEach之前执行。

这可能有助于说明所有钩子的执行顺序。下面来做个比较

1
2
3
4
5
6
7
8
9
10
11
12
beforeAll(() => console.log('1 - beforeAll'));
afterAll(() => console.log('1 - afterAll'));
beforeEach(() => console.log('1 - beforeEach'));
afterEach(() => console.log('1 - afterEach'));
test('', () => console.log('1 - test'));
describe('Scoped / Nested block', () => {
beforeAll(() => console.log('2 - beforeAll'));
afterAll(() => console.log('2 - afterAll'));
beforeEach(() => console.log('2 - beforeEach'));
afterEach(() => console.log('2 - afterEach'));
test('', () => console.log('2 - test'));
});

运行npm test得到结果如下

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
38
39
40
41
42
43
44
45
46
xx@xx test /Users/durban/nodejs/webpack-react-demo
> jest --notify --watchman=false

PASS src/__tests__/jest_setup_describe_diff.js
● Console

console.log src/__tests__/jest_setup_describe_diff.js:1
1 - beforeAll
console.log src/__tests__/jest_setup_describe_diff.js:3
1 - beforeEach
console.log src/__tests__/jest_setup_describe_diff.js:5
1 - test
console.log src/__tests__/jest_setup_describe_diff.js:4
1 - afterEach
console.log src/__tests__/jest_setup_describe_diff.js:7
2 - beforeAll
console.log src/__tests__/jest_setup_describe_diff.js:3
1 - beforeEach
console.log src/__tests__/jest_setup_describe_diff.js:9
2 - beforeEach
console.log src/__tests__/jest_setup_describe_diff.js:11
2 - test
console.log src/__tests__/jest_setup_describe_diff.js:10
2 - afterEach
console.log src/__tests__/jest_setup_describe_diff.js:4
1 - afterEach
console.log src/__tests__/jest_setup_describe_diff.js:8
2 - afterAll
console.log src/__tests__/jest_setup_describe_diff.js:2
1 - afterAll

PASS src/__tests__/jest_async_promise.test.js (6.439s)
PASS src/__tests__/jest_setup_describe.js (7.13s)
PASS src/__tests__/jest_async_await.test.js (6.063s)
PASS src/__tests__/jest_async_callback.test.js
PASS src/__tests__/jest_common.test.js
PASS src/__tests__/CheckboxWithLabelComponent.test.jsx
PASS src/__tests__/sum.test.js
PASS src/__tests__/jest_setup_each_onetime.test.js
PASS src/__tests__/jest_setup_each_moretime.test.js

Test Suites: 10 passed, 10 total
Tests: 32 passed, 32 total
Snapshots: 0 total
Time: 12.892s
Ran all test suites.

可以从这里看出其执行的顺序

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
● Console

console.log src/__tests__/jest_setup_describe_diff.js:1
1 - beforeAll
console.log src/__tests__/jest_setup_describe_diff.js:3
1 - beforeEach
console.log src/__tests__/jest_setup_describe_diff.js:5
1 - test
console.log src/__tests__/jest_setup_describe_diff.js:4
1 - afterEach
console.log src/__tests__/jest_setup_describe_diff.js:7
2 - beforeAll
console.log src/__tests__/jest_setup_describe_diff.js:3
1 - beforeEach
console.log src/__tests__/jest_setup_describe_diff.js:9
2 - beforeEach
console.log src/__tests__/jest_setup_describe_diff.js:11
2 - test
console.log src/__tests__/jest_setup_describe_diff.js:10
2 - afterEach
console.log src/__tests__/jest_setup_describe_diff.js:4
1 - afterEach
console.log src/__tests__/jest_setup_describe_diff.js:8
2 - afterAll
console.log src/__tests__/jest_setup_describe_diff.js:2
1 - afterAll

Order of execution of describe and test blocks - describe和test的执行顺序

在一个测试文件中Jest在执行真实的测试之前先执行所有describe的handlers。

这是在before*和after*的handlers中进行setup和teardown的另一个原因而不是在describe blocks。

一旦describe blocks完成,默认情况下,Jest将按照它们在collection phase遇到的顺序依次运行所有测试,等待每个测试完成并在继续之前进行整理。

理解起来很难,看下下面的例子,考虑下下面的代码猜测下输出的顺序是什么:

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
describe('outer', () => {
console.log('describe outer-a');

describe('describe inner 1', () => {
console.log('describe inner 1');
test('test 1', () => {
console.log('test for describe inner 1');
expect(true).toEqual(true);
});
});

console.log('describe outer-b');

test('test 1', () => {
console.log('test for describe outer');
expect(true).toEqual(true);
});

describe('describe inner 2', () => {
console.log('describe inner 2');
test('test for describe inner 2', () => {
console.log('test for describe inner 2');
expect(false).toEqual(false);
});
});

console.log('describe outer-c');
});

执行npm test,看下下面这块的输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
● Console

console.log src/__tests__/jest_setup_describe_order.js:2
describe outer-a
console.log src/__tests__/jest_setup_describe_order.js:5
describe inner 1
console.log src/__tests__/jest_setup_describe_order.js:12
describe outer-b
console.log src/__tests__/jest_setup_describe_order.js:20
describe inner 2
console.log src/__tests__/jest_setup_describe_order.js:27
describe outer-c
console.log src/__tests__/jest_setup_describe_order.js:7
test for describe inner 1
console.log src/__tests__/jest_setup_describe_order.js:15
test for describe outer
console.log src/__tests__/jest_setup_describe_order.js:22
test for describe inner 2

从输出中可以看出

1
2
3
4
5
describe outer-a
describe inner 1
describe outer-b
describe inner 2
describe outer-c

这几行的输出表示从外到内的执行了describe里面的代码,并没有按照顺序执行测试模块,而是在执行完describe只有,在从上到下的按照顺序执行测试模块,这个顺序要好好理解,对于以后的写测试模块的逻辑非常重要。

建议

如果一个测试失败了,首先要检查的事情应该是当测试单独运行的时候测试是否失败。

在Jest中,只运行一个测试很简单 - 只需暂时将该测试命令更改为test.only,如下

1
2
3
4
5
6
7
test.only('this will be the only test that runs', () => {
expect(true).toBe(false);
});

test('this test will not run', () => {
expect('A').toBe('A');
});

这次换个测试命令,不然每次执行npm test会把之前的也一起执行了,命令如下

1
npx jest src/__tests__/jest_setup_test_only.js

会得到如下的输出

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
FAIL  src/__tests__/jest_setup_test_only.js
✕ this will be the only test that runs (10ms)
○ skipped 1 test

● this will be the only test that runs

expect(received).toBe(expected) // Object.is equality

Expected: false
Received: true

1 | test.only('this will be the only test that runs', () => {
> 2 | expect(true).toBe(false);
| ^
3 | });
4 |
5 | test('this test will not run', () => {

at Object.<anonymous> (src/__tests__/jest_setup_test_only.js:2:16)

Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 skipped, 2 total
Snapshots: 0 total
Time: 2.344s
Ran all test suites matching /src\/__tests__\/jest_setup_test_only.js/i.

从这里可以看出

1
2
✕ this will be the only test that runs (10ms)
○ skipped 1 test

有两个测试的但是其中一个被跳过了。

当有一个比较复杂的测试中有一个小的测试总是事变,但是单独运行的时候又是成功的,可能是的原因是不同的测试中的有一些干扰元素干扰了这个测试。可以通过用beforeEach清除一些共享状态来解决这个问题。如果不确定是共享的转状态是否被修改,可以通过加入一些日志来判断。

项目地址

1
2
https://github.com/durban89/webpack4-react16-reactrouter-demo.git
tag:v_1.0.19

项目初始化【这里使用之前的项目,节省时间】

项目地址

1
2
https://github.com/durban89/webpack4-react16-reactrouter-demo.git
tag:v_1.0.17

拉取

1
2
3
4
5
git clone https://github.com/durban89/webpack4-react16-reactrouter-demo.git 
cd webpack4-react16-reactrouter-demo
git fetch origin
git checkout v_1.0.17
npm install

一般写单元测试的时候有时候需要在测试运行之前做一个操作,当测试运行完之后有时候也需要执行一些操作,Jest则提供了类似的帮助函数来处理这类问题

Repeating Setup For Many Tests 多次安装

如果在很多测试中需要重复进行某个操作,则可以使用beforeEach和afterEach,

如下,两个测试中都与城市数据进行交互。在每个测试之前调用一个方法initCityDatabase(),并且在每次测试之后调用clearCityDatabase()方法。

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
let citys = [];
let time = 1;
const isCity = (city) => {
if (citys.indexOf(city) > -1) {
return true;
}
return false;
};

const initCityDatabase = () => new Promise((resolve, reject) => {
let promise;

try {
setTimeout(() => {
if (time === 1) {
citys.push('natasha1');
time += 1;
} else if (time === 2) {
citys.push('natasha2');
time += 1;
}
promise = resolve(citys);
}, 1000);
} catch (err) {
return reject(err);
}

return promise;
});

const clearCityDatabase = () => new Promise((resolve, reject) => {
let promise;
try {
setTimeout(() => {
citys = [];
promise = resolve(citys);
}, 1000);
} catch (err) {
return reject(err);
}

return promise;
});

beforeEach(() => initCityDatabase());

afterEach(() => clearCityDatabase());

test('The city database has natasha1', () => {
expect(isCity('natasha1')).toBeTruthy();
});

test('The city database has natasha2', () => {
expect(isCity('natasha2')).toBeTruthy();
});

执行npm test会得到如下结果

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
> xx@xx test /Users/durban/nodejs/webpack-react-demo
> jest --notify --watchman=false

PASS src/__tests__/jest_setup_each_moretime.test.js (5.115s)
● Console

console.log src/__tests__/jest_setup_each_moretime.test.js:16
moretime -> init time = 1
console.log src/__tests__/jest_setup_each_moretime.test.js:37
moretime -> clear time = 1
console.log src/__tests__/jest_setup_each_moretime.test.js:16
moretime -> init time = 2
console.log src/__tests__/jest_setup_each_moretime.test.js:37
moretime -> clear time = 2

PASS src/__tests__/jest_async_callback.test.js
PASS src/__tests__/jest_async_promise.test.js (6.436s)
PASS src/__tests__/jest_async_await.test.js (6.433s)
PASS src/__tests__/CheckboxWithLabelComponent.test.jsx
PASS src/__tests__/sum.test.js
PASS src/__tests__/jest_common.test.js

Test Suites: 7 passed, 7 total
Tests: 24 passed, 24 total
Snapshots: 0 total
Time: 8.91s
Ran all test suites.

从上面的例子可以看出有个地方不同

1
2
3
beforeEach(() => initCityDatabase());

afterEach(() => clearCityDatabase());

这里看不出我调用的函数是否进行了针对promise的操作,实际上上面的两行代码等于下面的几行代码

1
2
3
4
5
6
7
beforeEach(() => {
return initCityDatabase();
});

afterEach(() => {
return clearCityDatabase();
});

如果initCityDatabase和clearCityDatabase不是promise或者callback的函数完全可以直接调用不需要加return,这里是需要注意的地方,对于函数如果是callbacks方式的话,可以看前面的文章[React16 Jest单元测试 之 Testing Asynchronous Code]了解,还可以看出initCityDatabase和clearCityDatabase的调用次数

One-Time Setup 一次安装

在某些情况下,只需要在文件的开头进行一次安装。当安装的逻辑是异步的时候,这可能会特别麻烦,所以不能直接进行内联。

Jest提供beforeAll和afterAll来处理这种情况。如下,如果initCityDatabase和clearCityDatabase都返回了promise,并且城市数据可以在测试之间重复使用,那么我们可以将我们的测试代码更改为:

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
let citys = [];
let time1 = 1;
let time2 = 1;
const isCity = (city) => {
if (citys.indexOf(city) > -1) {
return true;
}
return false;
};

const initCityDatabase = () => new Promise((resolve, reject) => {
let promise;

try {
setTimeout(() => {
console.log('onetime -> init time = ', time1);
time1 += 1;
citys.push('natasha1');
citys.push('natasha2');
promise = resolve(citys);
}, 1000);
} catch (err) {
return reject(err);
}

return promise;
});

const clearCityDatabase = () => new Promise((resolve, reject) => {
let promise;
try {
console.log('onetime -> clear time = ', time2);
time2 += 1;
setTimeout(() => {
citys = [];
promise = resolve(citys);
}, 1000);
} catch (err) {
return reject(err);
}

return promise;
});

beforeAll(() => initCityDatabase());

afterAll(() => clearCityDatabase());

test('The city database has natasha1', () => {
expect(isCity('natasha1')).toBeTruthy();
});

test('The city database has natasha2', () => {
expect(isCity('natasha2')).toBeTruthy();
});

运行npm test得到类似如下结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
> webpack4_react16_reactrouter4 @1 .0 .0 test / Users / durban / nodejs / webpack - react - demo >
jest--notify--watchman = false

PASS src / __tests__ / jest_setup_each_moretime.test.js(5.234 s)
PASS src / __tests__ / jest_async_await.test.js(6.518 s)
PASS src / __tests__ / jest_async_promise.test.js(6.546 s)
PASS src / __tests__ / CheckboxWithLabelComponent.test.jsx
PASS src / __tests__ / jest_common.test.js
PASS src / __tests__ / sum.test.js
PASS src / __tests__ / jest_setup_each_onetime.test.js● Console

console.log src / __tests__ / jest_setup_each_onetime.test.js: 16
onetime - > init time = 1
console.log src / __tests__ / jest_setup_each_onetime.test.js: 32
onetime - > clear time = 1

PASS src / __tests__ / jest_async_callback.test.js

Test Suites: 8 passed, 8 total
Tests: 26 passed, 26 total
Snapshots: 0 total
Time: 10.007 s
Ran all test suites.

项目地址

1
2
https://github.com/durban89/webpack4-react16-reactrouter-demo.git
tag:v_1.0.18

正确安装思路是

1
2
3
4
sudo yum install https://centos7.iuscommunity.org/ius-release.rpm
sudo yum erase git
sudo yum install epel-release
sudo yum install git2u

没有问题的话可以正常安装,但是意外总是难以预料我将我遇到的情况记录下

执行第一个命令的时候遇到如下提示

1
2
3
Loaded plugins: langpacks
Cannot open: https://centos7.iuscommunity.org/ius-release.rpm. Skipping.
Error: Nothing to do

执行不了,我能力有限,我直接下载吧

1
curl https://centos7.iuscommunity.org/ius-release.rpm

还是不行呀,提示这个

那我换个wget

1
wget https://centos7.iuscommunity.org/ius-release.rpm

我去还是不行,提示如下

1
2
3
4
5
6
--2018-06-19 11:27:21--  https://centos7.iuscommunity.org/ius-release.rpm
Resolving centos7.iuscommunity.org (centos7.iuscommunity.org)... 162.242.221.48, 2001:4802:7801:102:be76:4eff:fe21:14aa
Connecting to centos7.iuscommunity.org (centos7.iuscommunity.org)|162.242.221.48|:443... connected.
ERROR: cannot verify centos7.iuscommunity.org's certificate, issued by ‘/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=Thawte TLS RSA CA G1’:
Unable to locally verify the issuer's authority.
To connect to centos7.iuscommunity.org insecurely, use `--no-check-certificate'.

OK,咱来个暴力点的加--no-check-certificate

1
wget https://centos7.iuscommunity.org/ius-release.rpm --no-check-certificate

终于可以了,那就继续安装

1
2
sudo yum install ius-release.rpm
sudo yum erase git

好吧,问题又来了,我们执行下面这个命令的时候

1
sudo yum install epel-release

我还是遇到了证书的问题

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
Could not retrieve mirrorlist https://mirrors.iuscommunity.org/mirrorlist?repo=ius-centos7&arch=x86_64&protocol=http error was
14: curl#60 - "Peer's Certificate issuer is not recognized."

One of the configured repositories failed (Unknown),
and yum doesn't have enough cached data to continue. At this point the only
safe thing yum can do is fail. There are a few ways to work "fix" this:

1. Contact the upstream for the repository and get them to fix the problem.

2. Reconfigure the baseurl/etc. for the repository, to point to a working
upstream. This is most often useful if you are using a newer
distribution release than is supported by the repository (and the
packages for the previous distribution release still work).

3. Disable the repository, so yum won't use it by default. Yum will then
just ignore the repository until you permanently enable it again or use
--enablerepo for temporary usage:

yum-config-manager --disable <repoid>

4. Configure the failing repository to be skipped, if it is unavailable.
Note that yum will try to contact the repo. when it runs most commands,
so will have to try and fail each time (and thus. yum will be be much
slower). If it is a very temporary problem though, this is often a nice
compromise:

yum-config-manager --save --setopt=<repoid>.skip_if_unavailable=true

Cannot find a valid baseurl for repo: ius/x86_64

老子也不知道咋回事,先说说解决办法,按照如下操作

1
2
3
4
5
sudo mv /etc/yum.repos.d/ius-archive.repo /etc/yum.repos.d/ius-archive.repo.backup
sudo mv /etc/yum.repos.d/ius-dev.repo /etc/yum.repos.d/ius-dev.repo.backup
sudo mv /etc/yum.repos.d/ius.repo /etc/yum.repos.d/ius.repo.backup
sudo mv /etc/yum.repos.d/ius-testing.repo /etc/yum.repos.d/ius-testing.repo.backup
sudo touch /etc/yum.repos.d/ius.repo

/etc/yum.repos.d/ius.repo的内容如下

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
[ius]
name=IUS Community Packages for Enterprise Linux 7 - $basearch
baseurl=https://mirrors.tuna.tsinghua.edu.cn/ius/stable/CentOS/7/$basearch
#mirrorlist=https://mirrors.iuscommunity.org/mirrorlist?repo=ius-centos7&arch=$basearch&protocol=http
failovermethod=priority
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/IUS-COMMUNITY-GPG-KEY

[ius-debuginfo]
name=IUS Community Packages for Enterprise Linux 7 - $basearch - Debug
baseurl=https://mirrors.tuna.tsinghua.edu.cn/ius/stable/CentOS/7/$basearch/debuginfo
#mirrorlist=https://mirrors.iuscommunity.org/mirrorlist?repo=ius-centos7-debuginfo&arch=$basearch&protocol=http
failovermethod=priority
enabled=0
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/IUS-COMMUNITY-GPG-KEY

[ius-source]
name=IUS Community Packages for Enterprise Linux 7 - $basearch - Source
baseurl=https://mirrors.tuna.tsinghua.edu.cn/ius/stable/CentOS/7/SRPMS
#mirrorlist=https://mirrors.iuscommunity.org/mirrorlist?repo=ius-centos7-source&arch=source&protocol=http
failovermethod=priority
enabled=0
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/IUS-COMMUNITY-GPG-KEY

然后在执行

1
2
sudo yum install epel-release 
sudo yum install git2u

安装成功,可喜可贺。

其实我们打开https://mirrors.iuscommunity.org/mirrorlist?repo=ius-centos7&arch=x86\_64&protocol=http 这个地址就可以发现,里面的内容全部都是镜像相关的地址信息,我们可以将baseurl中的mirrors.tuna.tsinghua.edu.cn这个地址换成其他的镜像地址,应该也是可以的,这个我就没有做过测试了,有兴趣的可以试下。

原因是由于什么呢?
其实安装的过程中提示已经说的很清楚了,就是证书的问题,但是在我的其他机器为什么又没有这个问题呢。

0%