0%

Unit Test with Jtest and Enzyme inside the ReactJS

从接触软件工程开始,编写单元测试就一直被不断强调,其重要性可想而知。那么,如何在我们的 React 项目中编写单元测试呢?

要知道,借助npx create-react-app [project-name] --template typescript命令,我们可以创建一个基于 TypeScript 的 React 项目,并且项目内部集成Jtest工具,通过运行yarn test命令,或者npm run test命令,能够进行单元测试。当然,前提是我们编写了相应的单元测试代码,即以.test.tsx或者.spec.tsx 后缀的文件。Jtest工具会自动检测改动并运行单元测试。那么,关键问题是,如何编写单元测试代码?

首先,我们需要借助Enzyme工具,作为React生态系统里一个通用工具,它方便了我们针对组件的行为编写测试。在项目根文件夹,运行命令:

1
$ npm install -D enzyme @types/enzyme enzyme-adapter-react-16 react-addons-test-utils

我们同时安装了enzyme@types/enzyme,前者包含实际运行的JavaScript代码包,而后者则包含了声明文件,以便TypeScript能够了解该如何使用Enzyme。此外,我们安装了react-addons-test-utils,以便于在我们的 React 项目中使用Enzyme编写单元测试代码。

接下来,我们通过一组示例,来看看Enzyme的正确打开方式。假定我们创建了一个基于 TypeScript 的 React 项目,那么其src下的目录大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
.
├── App.css
├── App.test.tsx
├── App.tsx
├── components
│   └── ...
├── index.css
├── index.tsx
├── logo.svg
├── react-app-env.d.ts
├── serviceWorker.ts
└── setupTests.ts

为了使用配合React@16使用Enzyme,编辑setupTests.ts,否则会在运行单元测试时出现报错:

1
2
3
4
5
6
7
8
9
10
11
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect';

// use enzyme
import { configure } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'

configure({ adapter: new Adapter() })

现在,我们可以编写一个Hello组件,并将其命名为Hello.tsx

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
import * as React from 'react';

export interface Props {
name: string,
enthusiasmLevel?: number
}

// Method I: Define React Component by function
function Hello({ name, enthusiasmLevel = 1}: Props) {
if (enthusiasmLevel <= 0) {
throw new Error('You could be a little more enthusiastic. :D');
}

return (
<div className="hello">
<div className="greeting">
Hello {name + getExclamationMarks(enthusiasmLevel)}
</div>
</div>
);
}

// Method II: Define React Component by class
/*
class Hello extends React.Component<Props, object> {
render() {
const { name, enthusiasmLevel = 1 } = this.props;

if (enthusiasmLevel <= 0) {
throw new Error('You could be a little more enthusiastic. :D');
}

return (
<div className="hello">
<div className="greeting">
Hello {name + getExclamationMarks(enthusiasmLevel)}
</div>
</div>
);
}
}
*/

export default Hello;

// helpers
function getExclamationMarks(numChars: number) {
return Array(numChars + 1).join('!')
}

那么,针对Hello组件的单元测试,可以在同一文件夹下创建文件Hello.test.tsx

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
import * as React from 'react';
import * as enzyme from 'enzyme';
import Hello from './Hello';


it('renders the correct text when no enthusiasm level is given', () => {
const hello = enzyme.shallow(<Hello name='Daniel' />);
expect(hello.find(".greeting").text()).toEqual('Hello Daniel!')
});

it('renders the correct text with an explicit enthusiasm of 1', () => {
const hello = enzyme.shallow(<Hello name='Daniel' enthusiasmLevel={1}/>);
expect(hello.find(".greeting").text()).toEqual('Hello Daniel!')
});

it('renders the correct text with an explicit enthusiasm level of 5', () => {
const hello = enzyme.shallow(<Hello name='Daniel' enthusiasmLevel={5} />);
expect(hello.find(".greeting").text()).toEqual('Hello Daniel!!!!!');
});

it('throws when the enthusiasm level is 0', () => {
expect(() => {
enzyme.shallow(<Hello name='Daniel' enthusiasmLevel={0} />);
}).toThrow();
});

it('throws when the enthusiasm level is negative', () => {
expect(() => {
enzyme.shallow(<Hello name='Daniel' enthusiasmLevel={-1} />);
}).toThrow();
});

更多测试接口可以参看API Reference.

参考文献

使用Jest编写测试 - React - TypeScript 中文手册

以上。